├── public ├── favicon.ico ├── robots.txt ├── .htaccess ├── web.config └── index.php ├── database ├── .gitignore ├── seeds │ └── DatabaseSeeder.php ├── migrations │ ├── 2014_10_12_100000_create_password_resets_table.php │ ├── 2014_10_12_000000_create_users_table.php │ └── 2018_01_29_081239_create_members_table.php └── factories │ └── UserFactory.php ├── bootstrap ├── cache │ └── .gitignore └── app.php ├── storage ├── logs │ └── .gitignore ├── app │ ├── public │ │ └── .gitignore │ └── .gitignore └── framework │ ├── cache │ └── .gitignore │ ├── testing │ └── .gitignore │ ├── views │ └── .gitignore │ ├── sessions │ └── .gitignore │ └── .gitignore ├── .gitattributes ├── .gitignore ├── tests ├── TestCase.php ├── Unit │ ├── ExampleTest.php │ └── Domain │ │ └── Member │ │ ├── Address │ │ └── CountryTest.php │ │ └── PhoneNumber │ │ └── PhoneNumberTest.php ├── Feature │ └── ExampleTest.php └── CreatesApplication.php ├── resources ├── assets │ ├── sass │ │ ├── app.scss │ │ └── _variables.scss │ └── js │ │ ├── components │ │ └── ExampleComponent.vue │ │ ├── app.js │ │ └── bootstrap.js ├── lang │ └── en │ │ ├── pagination.php │ │ ├── auth.php │ │ ├── passwords.php │ │ └── validation.php └── views │ ├── home.blade.php │ ├── auth │ ├── passwords │ │ ├── email.blade.php │ │ └── reset.blade.php │ ├── login.blade.php │ └── register.blade.php │ ├── welcome.blade.php │ ├── layouts │ └── app.blade.php │ └── member │ └── profile.blade.php ├── app ├── Http │ ├── Middleware │ │ ├── EncryptCookies.php │ │ ├── VerifyCsrfToken.php │ │ ├── TrimStrings.php │ │ ├── RedirectIfAuthenticated.php │ │ └── TrustProxies.php │ ├── Controllers │ │ ├── Controller.php │ │ ├── HomeController.php │ │ ├── Auth │ │ │ ├── ForgotPasswordController.php │ │ │ ├── LoginController.php │ │ │ ├── ResetPasswordController.php │ │ │ └── RegisterController.php │ │ └── Member │ │ │ └── ProfileController.php │ ├── Requests │ │ └── Membership │ │ │ ├── RegisterMember.php │ │ │ └── UpdateProfile.php │ └── Kernel.php ├── Providers │ ├── BroadcastServiceProvider.php │ ├── AppServiceProvider.php │ ├── AuthServiceProvider.php │ ├── EventServiceProvider.php │ ├── DomainRepositoryServiceProvider.php │ ├── ApplicationRepositoryServiceProvider.php │ └── RouteServiceProvider.php ├── Console │ └── Kernel.php ├── Repositories │ ├── Authentication │ │ └── UserRepository.php │ └── Membership │ │ └── MemberRepository.php ├── Exceptions │ └── Handler.php └── Services │ └── Membership │ ├── RegisterMember.php │ └── UpdateProfile.php ├── routes ├── channels.php ├── api.php ├── console.php └── web.php ├── webpack.mix.js ├── src ├── Domain │ ├── Common │ │ ├── Exception │ │ │ └── Exception.php │ │ ├── ValueObject │ │ │ ├── Enum.php │ │ │ ├── BaseEnum.php │ │ │ ├── ValueObject.php │ │ │ └── BaseValueObject.php │ │ └── UniqueIdentifier │ │ │ ├── UniqueIdentifier.php │ │ │ └── BaseUniqueIdentifier.php │ └── Member │ │ ├── Exception │ │ ├── InvalidCountry.php │ │ └── InvalidPhoneNumber.php │ │ ├── Address │ │ ├── City.php │ │ ├── Street.php │ │ ├── Region.php │ │ ├── Country.php │ │ └── Address.php │ │ ├── FirstName.php │ │ ├── MemberRepository.php │ │ ├── LastName.php │ │ ├── MemberIdentifier.php │ │ ├── PhoneNumber │ │ ├── InternationalDialingCode.php │ │ ├── DomesticNumber.php │ │ ├── PhoneNumber.php │ │ └── CountryCallingCode.php │ │ └── Member.php └── Infrastructure │ └── Repositories │ └── Eloquent │ ├── Member │ ├── Repository.php │ ├── DomainRepository.php │ ├── Member.php │ └── ApplicationRepository.php │ └── User │ ├── User.php │ └── ApplicationRepository.php ├── server.php ├── .env.example ├── config ├── view.php ├── services.php ├── broadcasting.php ├── filesystems.php ├── queue.php ├── cache.php ├── auth.php ├── database.php ├── mail.php └── session.php ├── phpunit.xml ├── package.json ├── artisan ├── composer.json └── README.md /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite 2 | -------------------------------------------------------------------------------- /bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !public/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.css linguist-vendored 3 | *.scss linguist-vendored 4 | *.js linguist-vendored 5 | CHANGELOG.md export-ignore 6 | -------------------------------------------------------------------------------- /storage/framework/.gitignore: -------------------------------------------------------------------------------- 1 | config.php 2 | routes.php 3 | schedule-* 4 | compiled.php 5 | services.json 6 | events.scanned.php 7 | routes.scanned.php 8 | down 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /public/hot 3 | /public/storage 4 | /storage/*.key 5 | /vendor 6 | /.idea 7 | /.vagrant 8 | Homestead.json 9 | Homestead.yaml 10 | npm-debug.log 11 | yarn-error.log 12 | .env 13 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | call(UsersTableSeeder::class); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/Http/Middleware/EncryptCookies.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Http/Middleware/VerifyCsrfToken.php: -------------------------------------------------------------------------------- 1 | get('/'); 18 | 19 | $response->assertStatus(200); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Providers/BroadcastServiceProvider.php: -------------------------------------------------------------------------------- 1 | id === (int) $id; 16 | }); 17 | -------------------------------------------------------------------------------- /tests/CreatesApplication.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class)->bootstrap(); 20 | 21 | Hash::setRounds(4); 22 | 23 | return $app; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /routes/api.php: -------------------------------------------------------------------------------- 1 | get('/user', function (Request $request) { 17 | return $request->user(); 18 | }); 19 | -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | let mix = require('laravel-mix'); 2 | 3 | /* 4 | |-------------------------------------------------------------------------- 5 | | Mix Asset Management 6 | |-------------------------------------------------------------------------- 7 | | 8 | | Mix provides a clean, fluent API for defining some Webpack build steps 9 | | for your Laravel application. By default, we are compiling the Sass 10 | | file for the application as well as bundling up all the JS files. 11 | | 12 | */ 13 | 14 | mix.js('resources/assets/js/app.js', 'public/js') 15 | .sass('resources/assets/sass/app.scss', 'public/css'); 16 | -------------------------------------------------------------------------------- /app/Http/Controllers/HomeController.php: -------------------------------------------------------------------------------- 1 | middleware('auth'); 17 | } 18 | 19 | /** 20 | * Show the application dashboard. 21 | * 22 | * @return \Illuminate\Http\Response 23 | */ 24 | public function index() 25 | { 26 | return view('home'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Domain/Common/Exception/Exception.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @copyright Keith Mifsud 2018 11 | * 12 | * @since 1.0 13 | * @version 1.0 Initial Release 14 | */ 15 | 16 | namespace KeithMifsud\Demo\Domain\Common\Exception; 17 | 18 | /** 19 | * An extensible domain exception. 20 | * 21 | */ 22 | class Exception extends \Exception implements \Throwable 23 | { 24 | 25 | } 26 | -------------------------------------------------------------------------------- /resources/lang/en/pagination.php: -------------------------------------------------------------------------------- 1 | '« Previous', 17 | 'next' => 'Next »', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /routes/console.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 18 | })->describe('Display an inspiring quote'); 19 | -------------------------------------------------------------------------------- /server.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | $uri = urldecode( 11 | parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) 12 | ); 13 | 14 | // This file allows us to emulate Apache's "mod_rewrite" functionality from the 15 | // built-in PHP web server. This provides a convenient way to test a Laravel 16 | // application without having installed a "real" web server software here. 17 | if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) { 18 | return false; 19 | } 20 | 21 | require_once __DIR__.'/public/index.php'; 22 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews -Indexes 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Handle Authorization Header 9 | RewriteCond %{HTTP:Authorization} . 10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 11 | 12 | # Redirect Trailing Slashes If Not A Folder... 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteCond %{REQUEST_URI} (.+)/$ 15 | RewriteRule ^ %1 [L,R=301] 16 | 17 | # Handle Front Controller... 18 | RewriteCond %{REQUEST_FILENAME} !-d 19 | RewriteCond %{REQUEST_FILENAME} !-f 20 | RewriteRule ^ index.php [L] 21 | 22 | -------------------------------------------------------------------------------- /src/Domain/Member/Exception/InvalidCountry.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @copyright Keith Mifsud 2018 11 | * 12 | * @since 1.0 13 | * @version 1.0 Initial Release 14 | */ 15 | 16 | namespace KeithMifsud\Demo\Domain\Member\Exception; 17 | 18 | use KeithMifsud\Demo\Domain\Common\Exception\Exception; 19 | 20 | /** 21 | * Exception for invalid country. 22 | * 23 | */ 24 | final class InvalidCountry extends Exception 25 | { 26 | 27 | } 28 | -------------------------------------------------------------------------------- /app/Http/Middleware/RedirectIfAuthenticated.php: -------------------------------------------------------------------------------- 1 | check()) { 21 | return redirect('/home'); 22 | } 23 | 24 | return $next($request); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /resources/assets/js/components/ExampleComponent.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 24 | -------------------------------------------------------------------------------- /src/Domain/Member/Exception/InvalidPhoneNumber.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @copyright Keith Mifsud 2018 11 | * 12 | * @since 1.0 13 | * @version 1.0 Initial Release 14 | */ 15 | 16 | namespace KeithMifsud\Demo\Domain\Member\Exception; 17 | 18 | use KeithMifsud\Demo\Domain\Common\Exception\Exception; 19 | 20 | 21 | /** 22 | * Exception for invalid phone number. 23 | * 24 | */ 25 | final class InvalidPhoneNumber extends Exception 26 | { 27 | 28 | } 29 | -------------------------------------------------------------------------------- /resources/lang/en/auth.php: -------------------------------------------------------------------------------- 1 | 'These credentials do not match our records.', 17 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=Laravel 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_LOG_LEVEL=debug 6 | APP_URL=http://localhost 7 | 8 | DB_CONNECTION=mysql 9 | DB_HOST=127.0.0.1 10 | DB_PORT=3306 11 | DB_DATABASE=homestead 12 | DB_USERNAME=homestead 13 | DB_PASSWORD=secret 14 | 15 | BROADCAST_DRIVER=log 16 | CACHE_DRIVER=file 17 | SESSION_DRIVER=file 18 | SESSION_LIFETIME=120 19 | QUEUE_DRIVER=sync 20 | 21 | REDIS_HOST=127.0.0.1 22 | REDIS_PASSWORD=null 23 | REDIS_PORT=6379 24 | 25 | MAIL_DRIVER=smtp 26 | MAIL_HOST=smtp.mailtrap.io 27 | MAIL_PORT=2525 28 | MAIL_USERNAME=null 29 | MAIL_PASSWORD=null 30 | MAIL_ENCRYPTION=null 31 | 32 | PUSHER_APP_ID= 33 | PUSHER_APP_KEY= 34 | PUSHER_APP_SECRET= 35 | PUSHER_APP_CLUSTER=mt1 36 | -------------------------------------------------------------------------------- /resources/assets/js/app.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * First we will load all of this project's JavaScript dependencies which 4 | * includes Vue and other libraries. It is a great starting point when 5 | * building robust, powerful web applications using Vue and Laravel. 6 | */ 7 | 8 | require('./bootstrap'); 9 | 10 | window.Vue = require('vue'); 11 | 12 | /** 13 | * Next, we will create a fresh Vue application instance and attach it to 14 | * the page. Then, you may begin adding components to this application 15 | * or customize the JavaScript scaffolding to fit your unique needs. 16 | */ 17 | 18 | Vue.component('example-component', require('./components/ExampleComponent.vue')); 19 | 20 | const app = new Vue({ 21 | el: '#app' 22 | }); 23 | -------------------------------------------------------------------------------- /src/Domain/Common/ValueObject/Enum.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * @copyright Keith Mifsud 2018 10 | * 11 | * @since 1.0 12 | * @version 1.0 Initial Release 13 | */ 14 | 15 | 16 | namespace KeithMifsud\Demo\Domain\Common\ValueObject; 17 | 18 | /** 19 | * Contract for Enum type Value Objects. 20 | * 21 | */ 22 | interface Enum 23 | { 24 | 25 | /** 26 | * Gets the enumerator by value. 27 | * 28 | * @param $value 29 | * @return mixed 30 | */ 31 | public static function getByValue($value); 32 | } 33 | -------------------------------------------------------------------------------- /app/Providers/AuthServiceProvider.php: -------------------------------------------------------------------------------- 1 | 'KeithMifsud\Demo\Application\Policies\ModelPolicy', 17 | ]; 18 | 19 | /** 20 | * Register any authentication / authorization services. 21 | * 22 | * @return void 23 | */ 24 | public function boot() 25 | { 26 | $this->registerPolicies(); 27 | 28 | // 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /resources/views/home.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
Dashboard
9 | 10 |
11 | @if (session('status')) 12 |
13 | {{ session('status') }} 14 |
15 | @endif 16 | 17 | You are logged in! 18 |
19 |
20 |
21 |
22 |
23 | @endsection 24 | -------------------------------------------------------------------------------- /app/Providers/EventServiceProvider.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'KeithMifsud\Demo\Application\Listeners\EventListener', 18 | ], 19 | ]; 20 | 21 | /** 22 | * Register any events for your application. 23 | * 24 | * @return void 25 | */ 26 | public function boot() 27 | { 28 | parent::boot(); 29 | 30 | // 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Http/Middleware/TrustProxies.php: -------------------------------------------------------------------------------- 1 | 'FORWARDED', 24 | Request::HEADER_X_FORWARDED_FOR => 'X_FORWARDED_FOR', 25 | Request::HEADER_X_FORWARDED_HOST => 'X_FORWARDED_HOST', 26 | Request::HEADER_X_FORWARDED_PORT => 'X_FORWARDED_PORT', 27 | Request::HEADER_X_FORWARDED_PROTO => 'X_FORWARDED_PROTO', 28 | ]; 29 | } 30 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_100000_create_password_resets_table.php: -------------------------------------------------------------------------------- 1 | string('email')->index(); 18 | $table->string('token'); 19 | $table->timestamp('created_at')->nullable(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | Schema::dropIfExists('password_resets'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /resources/lang/en/passwords.php: -------------------------------------------------------------------------------- 1 | 'Passwords must be at least six characters and match the confirmation.', 17 | 'reset' => 'Your password has been reset!', 18 | 'sent' => 'We have e-mailed your password reset link!', 19 | 'token' => 'This password reset token is invalid.', 20 | 'user' => "We can't find a user with that e-mail address.", 21 | 22 | ]; 23 | -------------------------------------------------------------------------------- /database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | define(\KeithMifsud\Demo\Infrastructure\Repositories\Eloquent\User\User::class, function (Faker $faker) { 17 | return [ 18 | 'name' => $faker->name, 19 | 'email' => $faker->unique()->safeEmail, 20 | 'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret 21 | 'remember_token' => str_random(10), 22 | ]; 23 | }); 24 | -------------------------------------------------------------------------------- /src/Infrastructure/Repositories/Eloquent/Member/Repository.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @copyright Keith Mifsud 2018 11 | * 12 | * @since 1.0 13 | * @version 1.0 Initial Release 14 | */ 15 | 16 | namespace KeithMifsud\Demo\Infrastructure\Repositories\Eloquent\Member; 17 | 18 | /** 19 | * An extensible Member Repository. 20 | * 21 | */ 22 | abstract class Repository 23 | { 24 | 25 | /** 26 | * @var Member 27 | */ 28 | protected $model; 29 | 30 | 31 | /** 32 | * Repository constructor. 33 | * 34 | * @param Member $model 35 | */ 36 | public function __construct(Member $model) 37 | { 38 | $this->model = $model; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Domain/Member/Address/City.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @copyright Keith Mifsud 2018 11 | * 12 | * @since 1.0 13 | * @version 1.0 Initial Release 14 | */ 15 | 16 | namespace KeithMifsud\Demo\Domain\Member\Address; 17 | 18 | use KeithMifsud\Demo\Domain\Common\ValueObject\BaseValueObject; 19 | use KeithMifsud\Demo\Domain\Common\ValueObject\ValueObject; 20 | 21 | /** 22 | * The city. 23 | * 24 | */ 25 | final class City extends BaseValueObject implements ValueObject 26 | { 27 | 28 | 29 | /** 30 | * City constructor. 31 | * 32 | * @param string $city 33 | */ 34 | public function __construct(string $city) 35 | { 36 | $this->value = $city; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Domain/Member/FirstName.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @copyright Keith Mifsud 2018 11 | * 12 | * @since 1.0 13 | * @version 1.0 Initial Release 14 | */ 15 | 16 | namespace KeithMifsud\Demo\Domain\Member; 17 | 18 | use KeithMifsud\Demo\Domain\Common\ValueObject\ValueObject; 19 | use KeithMifsud\Demo\Domain\Common\ValueObject\BaseValueObject; 20 | 21 | /** 22 | * The first name of a member. 23 | */ 24 | final class FirstName extends BaseValueObject implements ValueObject 25 | { 26 | 27 | /** 28 | * FirstName constructor. 29 | * 30 | * @param string $firstName 31 | */ 32 | public function __construct(string $firstName) 33 | { 34 | $this->value = $firstName; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Domain/Member/MemberRepository.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * @copyright Keith Mifsud 2018 10 | * 11 | * @since 1.0 12 | * @version 1.0 Initial Release 13 | */ 14 | 15 | 16 | namespace KeithMifsud\Demo\Domain\Member; 17 | 18 | use KeithMifsud\Demo\Domain\Common\UniqueIdentifier\UniqueIdentifier; 19 | 20 | /** 21 | * The members' repository. 22 | * 23 | */ 24 | interface MemberRepository 25 | { 26 | 27 | /** 28 | * Gets an existing member's profile by its 29 | * identifier. 30 | * 31 | * @param UniqueIdentifier $memberIdentifier 32 | * @return \stdClass|null 33 | */ 34 | public function getExistingMemberProfile( 35 | UniqueIdentifier $memberIdentifier 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /app/Http/Requests/Membership/RegisterMember.php: -------------------------------------------------------------------------------- 1 | 'required|min:3', 33 | 'last_name' => 'required|min:3', 34 | 'email' => 'required|email|unique:users', 35 | 'password' => 'required|confirmed|min:6' 36 | ]; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Domain/Common/ValueObject/BaseEnum.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @copyright Keith Mifsud 2018 11 | * 12 | * @since 1.0 13 | * @version 1.0 Initial Release 14 | */ 15 | 16 | namespace KeithMifsud\Demo\Domain\Common\ValueObject; 17 | 18 | use MabeEnum\Enum; 19 | use KeithMifsud\Demo\Domain\Common\ValueObject\Enum as Enumerator; 20 | 21 | 22 | /** 23 | * An Adaptor for an Enum Value Object. 24 | * 25 | */ 26 | abstract class BaseEnum extends Enum 27 | { 28 | 29 | /** 30 | * Gets the enumerator by value. 31 | * 32 | * @param $value 33 | * @return static 34 | */ 35 | public static function getByValue($value) 36 | { 37 | return self::byValue($value); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/Domain/Member/LastName.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @copyright Keith Mifsud 2018 11 | * 12 | * @since 1.0 13 | * @version 1.0 Initial Release 14 | */ 15 | 16 | namespace KeithMifsud\Demo\Domain\Member; 17 | 18 | use KeithMifsud\Demo\Domain\Common\ValueObject\ValueObject; 19 | use KeithMifsud\Demo\Domain\Common\ValueObject\BaseValueObject; 20 | 21 | /** 22 | * The last name of the member. 23 | * 24 | */ 25 | final class LastName extends BaseValueObject implements ValueObject 26 | { 27 | 28 | 29 | /** 30 | * LastName constructor. 31 | * 32 | * @param string $lastName 33 | */ 34 | public function __construct(string $lastName) 35 | { 36 | $this->value = $lastName; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Domain/Member/Address/Street.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @copyright Keith Mifsud 2018 11 | * 12 | * @since 1.0 13 | * @version 1.0 Initial Release 14 | */ 15 | 16 | namespace KeithMifsud\Demo\Domain\Member\Address; 17 | 18 | use KeithMifsud\Demo\Domain\Common\ValueObject\BaseValueObject; 19 | use KeithMifsud\Demo\Domain\Common\ValueObject\ValueObject; 20 | 21 | 22 | /** 23 | * Street address. 24 | * 25 | */ 26 | final class Street extends BaseValueObject implements ValueObject 27 | { 28 | 29 | 30 | /** 31 | * Street constructor. 32 | * 33 | * @param string $streetAddress 34 | */ 35 | public function __construct(string $streetAddress) 36 | { 37 | $this->value = $streetAddress; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Domain/Member/Address/Region.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @copyright Keith Mifsud 2018 11 | * 12 | * @since 1.0 13 | * @version 1.0 Initial Release 14 | */ 15 | 16 | namespace KeithMifsud\Demo\Domain\Member\Address; 17 | 18 | 19 | use KeithMifsud\Demo\Domain\Common\ValueObject\BaseValueObject; 20 | use KeithMifsud\Demo\Domain\Common\ValueObject\ValueObject; 21 | 22 | 23 | /** 24 | * The region. 25 | * 26 | */ 27 | final class Region extends BaseValueObject implements ValueObject 28 | { 29 | 30 | 31 | /** 32 | * Region constructor. 33 | * 34 | * @param string $regionOrState 35 | */ 36 | public function __construct(string $regionOrState) 37 | { 38 | $this->value = $regionOrState; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /public/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/ForgotPasswordController.php: -------------------------------------------------------------------------------- 1 | middleware('guest'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /resources/assets/sass/_variables.scss: -------------------------------------------------------------------------------- 1 | 2 | // Body 3 | $body-bg: #f5f8fa; 4 | 5 | // Borders 6 | $laravel-border-color: darken($body-bg, 10%); 7 | $list-group-border: $laravel-border-color; 8 | $navbar-default-border: $laravel-border-color; 9 | $panel-default-border: $laravel-border-color; 10 | $panel-inner-border: $laravel-border-color; 11 | 12 | // Brands 13 | $brand-primary: #3097D1; 14 | $brand-info: #8eb4cb; 15 | $brand-success: #2ab27b; 16 | $brand-warning: #cbb956; 17 | $brand-danger: #bf5329; 18 | 19 | // Typography 20 | $icon-font-path: "~bootstrap-sass/assets/fonts/bootstrap/"; 21 | $font-family-sans-serif: "Raleway", sans-serif; 22 | $font-size-base: 14px; 23 | $line-height-base: 1.6; 24 | $text-color: #636b6f; 25 | 26 | // Navbar 27 | $navbar-default-bg: #fff; 28 | 29 | // Buttons 30 | $btn-default-color: $text-color; 31 | 32 | // Inputs 33 | $input-border: lighten($text-color, 40%); 34 | $input-border-focus: lighten($brand-primary, 25%); 35 | $input-color-placeholder: lighten($text-color, 30%); 36 | 37 | // Panels 38 | $panel-default-heading-bg: #fff; 39 | -------------------------------------------------------------------------------- /app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | command('inspire') 28 | // ->hourly(); 29 | } 30 | 31 | /** 32 | * Register the commands for the application. 33 | * 34 | * @return void 35 | */ 36 | protected function commands() 37 | { 38 | $this->load(__DIR__.'/Commands'); 39 | 40 | require base_path('routes/console.php'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 21 | $table->uuid('user_identifier')->unique(); 22 | $table->string('email')->unique(); 23 | $table->string('password'); 24 | $table->rememberToken(); 25 | $table->softDeletes(); 26 | $table->timestamps(); 27 | }); 28 | } 29 | 30 | /** 31 | * Reverse the migrations. 32 | * 33 | * @return void 34 | */ 35 | public function down() 36 | { 37 | Schema::dropIfExists('users'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /config/view.php: -------------------------------------------------------------------------------- 1 | [ 17 | resource_path('views'), 18 | ], 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Compiled View Path 23 | |-------------------------------------------------------------------------- 24 | | 25 | | This option determines where all the compiled Blade templates will be 26 | | stored for your application. Typically, this is within the storage 27 | | directory. However, as usual, you are free to change this value. 28 | | 29 | */ 30 | 31 | 'compiled' => realpath(storage_path('framework/views')), 32 | 33 | ]; 34 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | ./tests/Feature 14 | 15 | 16 | 17 | ./tests/Unit 18 | 19 | 20 | 21 | 22 | ./app 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/Domain/Common/ValueObject/ValueObject.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * @copyright Keith Mifsud 2018 10 | * 11 | * @since 1.0 12 | * @version 1.0 Initial Release 13 | */ 14 | 15 | 16 | namespace KeithMifsud\Demo\Domain\Common\ValueObject; 17 | 18 | /** 19 | * A contract for value objects. 20 | * 21 | */ 22 | interface ValueObject 23 | { 24 | 25 | /** 26 | * Gets the value. 27 | * 28 | * @return mixed 29 | */ 30 | public function getValue(); 31 | 32 | 33 | /** 34 | * Gets the value as a string representation. 35 | * 36 | * @return string 37 | */ 38 | public function toString() : string ; 39 | 40 | /** 41 | * Determines whether this and the other value objects 42 | * have the same value. 43 | * 44 | * @param ValueObject $other 45 | * @return bool 46 | */ 47 | public function sameValueAs(ValueObject $other) : bool ; 48 | } 49 | -------------------------------------------------------------------------------- /src/Domain/Member/MemberIdentifier.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @copyright Keith Mifsud 2018 11 | * 12 | * @since 1.0 13 | * @version 1.0 Initial Release 14 | */ 15 | 16 | namespace KeithMifsud\Demo\Domain\Member; 17 | 18 | use KeithMifsud\Demo\Domain\Common\UniqueIdentifier\UniqueIdentifier; 19 | use KeithMifsud\Demo\Domain\Common\UniqueIdentifier\BaseUniqueIdentifier; 20 | use KeithMifsud\Demo\Domain\Common\ValueObject\BaseValueObject; 21 | use KeithMifsud\Demo\Domain\Common\ValueObject\ValueObject; 22 | 23 | /** 24 | * A member's unique identifier. 25 | * 26 | */ 27 | final class MemberIdentifier extends BaseValueObject implements 28 | ValueObject 29 | { 30 | 31 | /** 32 | * MemberIdentifier constructor. 33 | * 34 | * @param UniqueIdentifier $identifier 35 | */ 36 | public function __construct(UniqueIdentifier $identifier) 37 | { 38 | $this->value = $identifier; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/LoginController.php: -------------------------------------------------------------------------------- 1 | middleware('guest')->except('logout'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "npm run development", 5 | "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 6 | "watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 7 | "watch-poll": "npm run watch -- --watch-poll", 8 | "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js", 9 | "prod": "npm run production", 10 | "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js" 11 | }, 12 | "devDependencies": { 13 | "axios": "^0.17", 14 | "bootstrap-sass": "^3.3.7", 15 | "cross-env": "^5.1", 16 | "jquery": "^3.2", 17 | "laravel-mix": "^1.0", 18 | "lodash": "^4.17.4", 19 | "vue": "^2.5.7" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'domain' => env('MAILGUN_DOMAIN'), 19 | 'secret' => env('MAILGUN_SECRET'), 20 | ], 21 | 22 | 'ses' => [ 23 | 'key' => env('SES_KEY'), 24 | 'secret' => env('SES_SECRET'), 25 | 'region' => 'us-east-1', 26 | ], 27 | 28 | 'sparkpost' => [ 29 | 'secret' => env('SPARKPOST_SECRET'), 30 | ], 31 | 32 | 'stripe' => [ 33 | 'model' => \KeithMifsud\Demo\Infrastructure\Repositories\Eloquent\User\User::class, 34 | 'key' => env('STRIPE_KEY'), 35 | 'secret' => env('STRIPE_SECRET'), 36 | ], 37 | 38 | ]; 39 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/ResetPasswordController.php: -------------------------------------------------------------------------------- 1 | middleware('guest'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/Providers/DomainRepositoryServiceProvider.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @copyright Keith Mifsud 2018 11 | * 12 | * @since 1.0 13 | * @version 1.0 Initial Release 14 | */ 15 | 16 | namespace KeithMifsud\Demo\Application\Providers; 17 | 18 | use Illuminate\Support\ServiceProvider; 19 | use KeithMifsud\Demo\Domain\Member\MemberRepository; 20 | use KeithMifsud\Demo\Infrastructure\Repositories\Eloquent\Member\Member; 21 | use KeithMifsud\Demo\Infrastructure\Repositories\Eloquent\Member\DomainRepository; 22 | 23 | /** 24 | * Service provider for domain repositories. 25 | * 26 | * Handles repository implementation bindings. 27 | */ 28 | class DomainRepositoryServiceProvider extends ServiceProvider 29 | { 30 | 31 | /** 32 | * Registers the bindings. 33 | * 34 | */ 35 | public function register() 36 | { 37 | $this->app->bind( 38 | MemberRepository::class, 39 | DomainRepository::class 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Infrastructure/Repositories/Eloquent/Member/DomainRepository.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @copyright Keith Mifsud 2018 11 | * 12 | * @since 1.0 13 | * @version 1.0 Initial Release 14 | */ 15 | 16 | namespace KeithMifsud\Demo\Infrastructure\Repositories\Eloquent\Member; 17 | 18 | use KeithMifsud\Demo\Domain\Member\MemberRepository; 19 | use KeithMifsud\Demo\Domain\Common\UniqueIdentifier\UniqueIdentifier; 20 | 21 | /** 22 | * Eloquent implementation of the member domain 23 | * repository. 24 | * 25 | */ 26 | final class DomainRepository extends Repository implements MemberRepository 27 | { 28 | 29 | /** 30 | * Gets the member's profile. 31 | * 32 | * @param UniqueIdentifier $identifier 33 | * @return mixed 34 | */ 35 | public function getExistingMemberProfile(UniqueIdentifier $identifier) 36 | { 37 | return $this->model->where([ 38 | 'user_identifier' => $identifier->toString() 39 | ])->first(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Repositories/Authentication/UserRepository.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * @copyright Keith Mifsud 2018 10 | * 11 | * @since 1.0 12 | * @version 1.0 Initial Release 13 | */ 14 | 15 | namespace KeithMifsud\Demo\Application\Repositories\Authentication; 16 | 17 | use KeithMifsud\Demo\Domain\Common\UniqueIdentifier\UniqueIdentifier; 18 | 19 | /** 20 | * The application's user repository. 21 | * 22 | */ 23 | interface UserRepository 24 | { 25 | 26 | /** 27 | * Creates a new user. 28 | * 29 | * @param UniqueIdentifier $userIdentifier 30 | * @param string $emailAddress 31 | * @param string $password 32 | * @return mixed 33 | */ 34 | public function createNewUser( 35 | UniqueIdentifier $userIdentifier, 36 | string $emailAddress, 37 | string $password 38 | ); 39 | 40 | 41 | /** 42 | * Gets a user by its identifier. 43 | * 44 | * @param UniqueIdentifier $identifier 45 | * @return mixed 46 | */ 47 | public function getUserByIdentifier(UniqueIdentifier $identifier); 48 | 49 | } 50 | -------------------------------------------------------------------------------- /app/Exceptions/Handler.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * @copyright Keith Mifsud 2018 10 | * 11 | * @since 1.0 12 | * @version 1.0 Initial Release 13 | */ 14 | 15 | 16 | namespace KeithMifsud\Demo\Application\Repositories\Membership; 17 | 18 | use KeithMifsud\Demo\Domain\Member\Member; 19 | use KeithMifsud\Demo\Domain\Common\UniqueIdentifier\UniqueIdentifier; 20 | 21 | /** 22 | * The application's member repository. 23 | * 24 | */ 25 | interface MemberRepository 26 | { 27 | 28 | /** 29 | * Stores new member. 30 | * 31 | * @param Member $member 32 | * @return int|null 33 | */ 34 | public function storeNewMember(Member $member); 35 | 36 | 37 | /** 38 | * Updates a member's profile in storage. 39 | * 40 | * @param Member $member 41 | * @return mixed 42 | */ 43 | public function updateProfile( 44 | Member $member 45 | ); 46 | 47 | 48 | /** 49 | * Gets the member from storage. 50 | * 51 | * @param UniqueIdentifier $memberIdentifier 52 | * @return mixed 53 | */ 54 | public function getMember(UniqueIdentifier $memberIdentifier); 55 | } 56 | -------------------------------------------------------------------------------- /src/Domain/Common/ValueObject/BaseValueObject.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @copyright Keith Mifsud 2018 11 | * 12 | * @since 1.0 13 | * @version 1.0 Initial Release 14 | */ 15 | 16 | namespace KeithMifsud\Demo\Domain\Common\ValueObject; 17 | 18 | /** 19 | * An extensible value object. 20 | * 21 | */ 22 | abstract class BaseValueObject 23 | { 24 | 25 | /** 26 | * @var mixed 27 | */ 28 | protected $value; 29 | 30 | 31 | /** 32 | * Gets the value. 33 | * 34 | * @return mixed 35 | */ 36 | public function getValue() 37 | { 38 | return $this->value; 39 | } 40 | 41 | 42 | /** 43 | * Gets the value as a string. 44 | * 45 | * @return string 46 | */ 47 | public function toString(): string 48 | { 49 | return (string)$this->getValue(); 50 | } 51 | 52 | 53 | /** 54 | * Determines whether this and the other value objects 55 | * have the same value. 56 | * 57 | * @param ValueObject $other 58 | * @return bool 59 | */ 60 | public function sameValueAs(ValueObject $other) : bool 61 | { 62 | return $this->getValue() == $other->getValue(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /database/migrations/2018_01_29_081239_create_members_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 21 | $table->uuid('user_identifier')->unique(); 22 | $table->string('first_name'); 23 | $table->string('last_name'); 24 | $table->string('international_dialling_code')->nullable(); 25 | $table->string('domestic_phone_number')->nullable(); 26 | $table->string('street_address')->nullable(); 27 | $table->string('city')->nullable(); 28 | $table->string('region')->nullable(); 29 | $table->string('country_code')->nullable(); 30 | $table->string('country')->nullable(); 31 | $table->softDeletes(); 32 | $table->timestamps(); 33 | }); 34 | } 35 | 36 | /** 37 | * Reverse the migrations. 38 | * 39 | * @return void 40 | */ 41 | public function down() 42 | { 43 | Schema::dropIfExists('members'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Domain/Common/UniqueIdentifier/UniqueIdentifier.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * @copyright Keith Mifsud 2018 10 | * 11 | * @since 1.0 12 | * @version 1.0 Initial Release 13 | */ 14 | 15 | 16 | namespace KeithMifsud\Demo\Domain\Common\UniqueIdentifier; 17 | 18 | /** 19 | * Unique identifier contract. 20 | * 21 | */ 22 | interface UniqueIdentifier 23 | { 24 | 25 | /** 26 | * Generates a unique identifier. 27 | * 28 | * @return mixed 29 | */ 30 | public static function generate(): UniqueIdentifier; 31 | 32 | 33 | /** 34 | * Gets an identifier from its string. 35 | * 36 | * @param string $identifier 37 | * @return UniqueIdentifier 38 | */ 39 | public static function fromString(string $identifier): UniqueIdentifier; 40 | 41 | 42 | /** 43 | * Gets the identifier as a string. 44 | * 45 | * @return string 46 | */ 47 | public function toString(): string; 48 | 49 | 50 | /** 51 | * Determines if both identifier have the 52 | * same value. 53 | * 54 | * @param UniqueIdentifier $otherIdentifier 55 | * @return bool 56 | */ 57 | public function sameValueAs(UniqueIdentifier $otherIdentifier): bool; 58 | } 59 | -------------------------------------------------------------------------------- /src/Infrastructure/Repositories/Eloquent/User/User.php: -------------------------------------------------------------------------------- 1 | attributes['password'] = Hash::make($value); 50 | } 51 | 52 | 53 | /** 54 | * User has one Member. 55 | * 56 | * @return \Illuminate\Database\Eloquent\Relations\HasOne 57 | */ 58 | public function member() 59 | { 60 | return $this->hasOne(Member::class, 'user_identifier'); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Infrastructure/Repositories/Eloquent/Member/Member.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @copyright Keith Mifsud 2018 11 | * 12 | * @since 1.0 13 | * @version 1.0 Initial Release 14 | */ 15 | 16 | namespace KeithMifsud\Demo\Infrastructure\Repositories\Eloquent\Member; 17 | 18 | use Illuminate\Database\Eloquent\Model; 19 | use KeithMifsud\Demo\Infrastructure\Repositories\Eloquent\User\User; 20 | 21 | /** 22 | * The Member Eloquent data model. 23 | * 24 | */ 25 | class Member extends Model 26 | { 27 | 28 | /** 29 | * @var string 30 | */ 31 | protected $table = 'members'; 32 | 33 | 34 | /** 35 | * @var array 36 | */ 37 | protected $fillable = [ 38 | 'user_identifier', 39 | 'first_name', 40 | 'last_name', 41 | 'international_dialling_code', 42 | 'domestic_phone_number', 43 | 'street_address', 44 | 'city', 45 | 'region', 46 | 'country_code', 47 | 'city', 48 | ]; 49 | 50 | 51 | /** 52 | * Member belongs to User. 53 | * 54 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 55 | */ 56 | public function user() 57 | { 58 | return $this->belongsTo(User::class, 'user_identifier'); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/Providers/ApplicationRepositoryServiceProvider.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @copyright Keith Mifsud 2018 11 | * 12 | * @since 1.0 13 | * @version 1.0 Initial Release 14 | */ 15 | 16 | namespace KeithMifsud\Demo\Application\Providers; 17 | 18 | use Illuminate\Support\ServiceProvider; 19 | use KeithMifsud\Demo\Application\Repositories\Membership\MemberRepository; 20 | use KeithMifsud\Demo\Application\Repositories\Authentication\UserRepository; 21 | use KeithMifsud\Demo\Infrastructure\Repositories\Eloquent\User\ApplicationRepository; 22 | use KeithMifsud\Demo\Infrastructure\Repositories\Eloquent\Member\ApplicationRepository as MemberApplicationRepository; 23 | 24 | 25 | /** 26 | * A service provider for the application repositories 27 | * implementation bindings. 28 | * 29 | */ 30 | class ApplicationRepositoryServiceProvider extends ServiceProvider 31 | { 32 | 33 | /** 34 | * Registers the bindings. 35 | * 36 | */ 37 | public function register() 38 | { 39 | $this->app->bind( 40 | UserRepository::class, 41 | ApplicationRepository::class 42 | ); 43 | 44 | $this->app->bind( 45 | MemberRepository::class, 46 | MemberApplicationRepository::class 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/Http/Requests/Membership/UpdateProfile.php: -------------------------------------------------------------------------------- 1 | user_identifier == 23 | $this->request->get('user_identifier') 24 | ); 25 | } 26 | 27 | 28 | /** 29 | * Get the validation rules that apply to the request. 30 | * 31 | * @return array 32 | */ 33 | public function rules() 34 | { 35 | $rules = []; 36 | 37 | 38 | if (($this->filled('domestic_phone_number')) || 39 | ($this->filled('international_dialling_code')) 40 | ) { 41 | $rules['international_dialling_code'] = 'required|numeric'; 42 | $rules['domestic_phone_number'] = 'required|numeric|min:6'; 43 | } 44 | 45 | if (($this->filled('street_address')) || 46 | ($this->filled('city')) || 47 | ($this->filled('region')) || 48 | ($this->filled('country_code')) 49 | ) { 50 | $rules['street_address'] = 'required|min:8'; 51 | $rules['city'] = 'required|min:4'; 52 | $rules['region'] = 'required|min:4'; 53 | $rules['country_code'] = 'required|min:3'; 54 | } 55 | 56 | return $rules; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Domain/Member/PhoneNumber/InternationalDialingCode.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @copyright Keith Mifsud 2018 11 | * 12 | * @since 1.0 13 | * @version 1.0 Initial Release 14 | */ 15 | 16 | namespace KeithMifsud\Demo\Domain\Member\PhoneNumber; 17 | 18 | use KeithMifsud\Demo\Domain\Member\PhoneNumber\CountryCallingCode; 19 | use KeithMifsud\Demo\Domain\Common\ValueObject\ValueObject; 20 | use KeithMifsud\Demo\Domain\Common\ValueObject\BaseValueObject; 21 | use KeithMifsud\Demo\Domain\Member\Exception\InvalidPhoneNumber; 22 | 23 | 24 | /** 25 | * The phone number's international 26 | * dialing code. 27 | * 28 | */ 29 | final class InternationalDialingCode extends BaseValueObject implements ValueObject 30 | { 31 | 32 | /** 33 | * InternationalDialingCode constructor. 34 | * 35 | * @param string $countryCode 36 | * @throws 37 | */ 38 | public function __construct(string $countryCode) 39 | { 40 | try { 41 | $enumeratedCode = CountryCallingCode::getByValue($countryCode); 42 | 43 | } catch (\Exception $exception) { 44 | throw new InvalidPhoneNumber( 45 | "Invalid international dialing code: " . (string)$countryCode 46 | ); 47 | } 48 | $this->value = $enumeratedCode; 49 | } 50 | 51 | 52 | /** 53 | * Gets the value. 54 | * 55 | * @return string 56 | */ 57 | public function getValue(): string 58 | { 59 | return parent::getValue()->getValue(); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | name('login'); 21 | Route::post('/login', 'Auth\LoginController@login'); 22 | Route::post('/logout', 'Auth\LoginController@logout')->name('logout'); 23 | 24 | // Password reset routes 25 | Route::post( 26 | '/password/email', 27 | 'Auth\ForgotPasswordController@sendResetLinkEmail' 28 | )->name('password.email'); 29 | 30 | Route::get( 31 | '/password/reset', 32 | 'Auth\ForgotPasswordController@showLinkRequestForm' 33 | )->name('password.request'); 34 | 35 | Route::post('/password/reset', 'Auth\ResetPasswordController@reset'); 36 | 37 | Route::get( 38 | '/password/reset/{token}', 39 | 'Auth\ResetPasswordController@showResetForm' 40 | )->name('password.reset'); 41 | 42 | // Registration routes. 43 | Route::get( 44 | '/register', 45 | 'Auth\RegisterController@showRegistrationForm' 46 | )->name('register'); 47 | 48 | Route::post('/register', 'Auth\RegisterController@registerMember'); 49 | 50 | // Dashboard routes 51 | Route::get('/home', 'HomeController@index')->name('home'); 52 | 53 | Route::get( 54 | '/profile', 55 | 'Member\ProfileController@showProfileForm' 56 | )->name('profile'); 57 | 58 | Route::post('/profile', 'Member\ProfileController@updateProfile'); 59 | -------------------------------------------------------------------------------- /config/broadcasting.php: -------------------------------------------------------------------------------- 1 | env('BROADCAST_DRIVER', 'null'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Broadcast Connections 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may define all of the broadcast connections that will be used 26 | | to broadcast events to other systems or over websockets. Samples of 27 | | each available type of connection are provided inside this array. 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'pusher' => [ 34 | 'driver' => 'pusher', 35 | 'key' => env('PUSHER_APP_KEY'), 36 | 'secret' => env('PUSHER_APP_SECRET'), 37 | 'app_id' => env('PUSHER_APP_ID'), 38 | 'options' => [ 39 | 'cluster' => env('PUSHER_APP_CLUSTER'), 40 | 'encrypted' => true, 41 | ], 42 | ], 43 | 44 | 'redis' => [ 45 | 'driver' => 'redis', 46 | 'connection' => 'default', 47 | ], 48 | 49 | 'log' => [ 50 | 'driver' => 'log', 51 | ], 52 | 53 | 'null' => [ 54 | 'driver' => 'null', 55 | ], 56 | 57 | ], 58 | 59 | ]; 60 | -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | make(Illuminate\Contracts\Console\Kernel::class); 34 | 35 | $status = $kernel->handle( 36 | $input = new Symfony\Component\Console\Input\ArgvInput, 37 | new Symfony\Component\Console\Output\ConsoleOutput 38 | ); 39 | 40 | /* 41 | |-------------------------------------------------------------------------- 42 | | Shutdown The Application 43 | |-------------------------------------------------------------------------- 44 | | 45 | | Once Artisan has finished running, we will fire off the shutdown events 46 | | so that any final work may be done by the application before we shut 47 | | down the process. This is the last thing to happen to the request. 48 | | 49 | */ 50 | 51 | $kernel->terminate($input, $status); 52 | 53 | exit($status); 54 | -------------------------------------------------------------------------------- /resources/assets/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | 2 | window._ = require('lodash'); 3 | 4 | /** 5 | * We'll load jQuery and the Bootstrap jQuery plugin which provides support 6 | * for JavaScript based Bootstrap features such as modals and tabs. This 7 | * code may be modified to fit the specific needs of your application. 8 | */ 9 | 10 | try { 11 | window.$ = window.jQuery = require('jquery'); 12 | 13 | require('bootstrap-sass'); 14 | } catch (e) {} 15 | 16 | /** 17 | * We'll load the axios HTTP library which allows us to easily issue requests 18 | * to our Laravel back-end. This library automatically handles sending the 19 | * CSRF token as a header based on the value of the "XSRF" token cookie. 20 | */ 21 | 22 | window.axios = require('axios'); 23 | 24 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; 25 | 26 | /** 27 | * Next we will register the CSRF Token as a common header with Axios so that 28 | * all outgoing HTTP requests automatically have it attached. This is just 29 | * a simple convenience so we don't have to attach every token manually. 30 | */ 31 | 32 | let token = document.head.querySelector('meta[name="csrf-token"]'); 33 | 34 | if (token) { 35 | window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content; 36 | } else { 37 | console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token'); 38 | } 39 | 40 | /** 41 | * Echo exposes an expressive API for subscribing to channels and listening 42 | * for events that are broadcast by Laravel. Echo and event broadcasting 43 | * allows your team to easily build robust real-time web applications. 44 | */ 45 | 46 | // import Echo from 'laravel-echo' 47 | 48 | // window.Pusher = require('pusher-js'); 49 | 50 | // window.Echo = new Echo({ 51 | // broadcaster: 'pusher', 52 | // key: 'your-pusher-key', 53 | // cluster: 'mt1', 54 | // encrypted: true 55 | // }); 56 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | singleton( 30 | Illuminate\Contracts\Http\Kernel::class, 31 | KeithMifsud\Demo\Application\Http\Kernel::class 32 | ); 33 | 34 | $app->singleton( 35 | Illuminate\Contracts\Console\Kernel::class, 36 | KeithMifsud\Demo\Application\Console\Kernel::class 37 | ); 38 | 39 | $app->singleton( 40 | Illuminate\Contracts\Debug\ExceptionHandler::class, 41 | KeithMifsud\Demo\Application\Exceptions\Handler::class 42 | ); 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Return The Application 47 | |-------------------------------------------------------------------------- 48 | | 49 | | This script returns the application instance. The instance is given to 50 | | the calling script so we can separate the building of the instances 51 | | from the actual running of the application and sending responses. 52 | | 53 | */ 54 | 55 | return $app; 56 | -------------------------------------------------------------------------------- /app/Providers/RouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | mapApiRoutes(); 39 | 40 | $this->mapWebRoutes(); 41 | 42 | // 43 | } 44 | 45 | /** 46 | * Define the "web" routes for the application. 47 | * 48 | * These routes all receive session state, CSRF protection, etc. 49 | * 50 | * @return void 51 | */ 52 | protected function mapWebRoutes() 53 | { 54 | Route::middleware('web') 55 | ->namespace($this->namespace) 56 | ->group(base_path('routes/web.php')); 57 | } 58 | 59 | /** 60 | * Define the "api" routes for the application. 61 | * 62 | * These routes are typically stateless. 63 | * 64 | * @return void 65 | */ 66 | protected function mapApiRoutes() 67 | { 68 | Route::prefix('api') 69 | ->middleware('api') 70 | ->namespace($this->namespace) 71 | ->group(base_path('routes/api.php')); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "keithmifsud/laravel-ddd-demo", 3 | "description": "A demo application.", 4 | "keywords": ["framework", "laravel", "ddd", "domain driven design"], 5 | "license": "MIT", 6 | "type": "project", 7 | "require": { 8 | "php": ">=7.0.0", 9 | "fideloper/proxy": "~3.3", 10 | "laravel/framework": "5.5.*", 11 | "laravel/tinker": "~1.0", 12 | "marc-mabe/php-enum": "^3.0" 13 | }, 14 | "require-dev": { 15 | "filp/whoops": "~2.0", 16 | "fzaninotto/faker": "~1.4", 17 | "mockery/mockery": "~1.0", 18 | "phpunit/phpunit": "~6.0", 19 | "symfony/thanks": "^1.0" 20 | }, 21 | "autoload": { 22 | "classmap": [ 23 | "database/seeds", 24 | "database/factories" 25 | ], 26 | "psr-4": { 27 | "KeithMifsud\\Demo\\Application\\": "app/", 28 | "KeithMifsud\\Demo\\Domain\\": "src/Domain", 29 | "KeithMifsud\\Demo\\Infrastructure\\": "src/Infrastructure" 30 | } 31 | }, 32 | "autoload-dev": { 33 | "psr-4": { 34 | "KeithMifsud\\Demo\\Tests\\": "tests/" 35 | } 36 | }, 37 | "extra": { 38 | "laravel": { 39 | "dont-discover": [ 40 | ] 41 | } 42 | }, 43 | "scripts": { 44 | "post-root-package-install": [ 45 | "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" 46 | ], 47 | "post-create-project-cmd": [ 48 | "@php artisan key:generate" 49 | ], 50 | "post-autoload-dump": [ 51 | "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", 52 | "@php artisan package:discover" 53 | ] 54 | }, 55 | "config": { 56 | "preferred-install": "dist", 57 | "sort-packages": true, 58 | "optimize-autoloader": true 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Domain/Member/Address/Country.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @copyright Keith Mifsud 2018 11 | * 12 | * @since 1.0 13 | * @version 1.0 Initial Release 14 | */ 15 | 16 | namespace KeithMifsud\Demo\Domain\Member\Address; 17 | 18 | 19 | use KeithMifsud\Demo\Domain\Common\ValueObject\BaseEnum; 20 | use KeithMifsud\Demo\Domain\Common\ValueObject\BaseValueObject; 21 | use KeithMifsud\Demo\Domain\Member\Address\CountryEnum; 22 | use KeithMifsud\Demo\Domain\Common\ValueObject\Enum; 23 | use KeithMifsud\Demo\Domain\Common\ValueObject\ValueObject; 24 | use KeithMifsud\Demo\Domain\Member\Exception\InvalidCountry; 25 | 26 | /** 27 | * The country enumerator. 28 | * 29 | */ 30 | final class Country extends BaseValueObject implements ValueObject 31 | { 32 | 33 | /** 34 | * Country constructor. 35 | * 36 | * @param string $countryCode 37 | * @throws InvalidCountry 38 | */ 39 | public function __construct(string $countryCode) 40 | { 41 | try { 42 | $enumeratedCountry = CountryEnum::$countryCode(); 43 | } catch (\Exception $exception) { 44 | throw new InvalidCountry( 45 | "Invalid country code supplied " . (string)$countryCode 46 | ); 47 | } 48 | 49 | $this->value = $enumeratedCountry; 50 | } 51 | 52 | 53 | /** 54 | * Gets the country's value. 55 | * 56 | * @return bool|float|int|null|string 57 | */ 58 | public function getValue() 59 | { 60 | return parent::getValue()->getValue(); 61 | } 62 | 63 | 64 | /** 65 | * Gets the country's code in ISO Alpha 3 format. 66 | * 67 | * @return string 68 | */ 69 | public function getCode(): string 70 | { 71 | return $this->value->getName(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Domain/Member/PhoneNumber/DomesticNumber.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @copyright Keith Mifsud 2018 11 | * 12 | * @since 1.0 13 | * @version 1.0 Initial Release 14 | */ 15 | 16 | namespace KeithMifsud\Demo\Domain\Member\PhoneNumber; 17 | 18 | use KeithMifsud\Demo\Domain\Common\ValueObject\ValueObject; 19 | use KeithMifsud\Demo\Domain\Common\ValueObject\BaseValueObject; 20 | use KeithMifsud\Demo\Domain\Member\Exception\InvalidPhoneNumber; 21 | 22 | 23 | /** 24 | * The local part of a phone number. 25 | * 26 | * Includes the area code, the prefix and the line number combined 27 | * and without validation. 28 | * 29 | */ 30 | final class DomesticNumber extends BaseValueObject implements ValueObject 31 | { 32 | 33 | /** 34 | * @var int 35 | */ 36 | protected static $minLength = 5; 37 | 38 | 39 | /** 40 | * DomesticNumber constructor. 41 | * 42 | * @param string $localPhoneNumber 43 | * @throws InvalidPhoneNumber 44 | */ 45 | public function __construct(string $localPhoneNumber) 46 | { 47 | if (!self::isValid($localPhoneNumber)) { 48 | throw new InvalidPhoneNumber( 49 | "Invalid phone number: " . (string)$localPhoneNumber 50 | ); 51 | } 52 | $this->value = $localPhoneNumber; 53 | } 54 | 55 | 56 | /** 57 | * Determines if the given phone number 58 | * meets the specification. 59 | * 60 | * @param string $phoneNumber 61 | * @return bool 62 | */ 63 | public static function isValid(string $phoneNumber): bool 64 | { 65 | $meetsLengthRequirements = (strlen($phoneNumber) >= static::$minLength); 66 | $allNumeric = ctype_digit($phoneNumber); 67 | 68 | return $meetsLengthRequirements && $allNumeric; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | define('LARAVEL_START', microtime(true)); 11 | 12 | /* 13 | |-------------------------------------------------------------------------- 14 | | Register The Auto Loader 15 | |-------------------------------------------------------------------------- 16 | | 17 | | Composer provides a convenient, automatically generated class loader for 18 | | our application. We just need to utilize it! We'll simply require it 19 | | into the script here so that we don't have to worry about manual 20 | | loading any of our classes later on. It feels great to relax. 21 | | 22 | */ 23 | 24 | require __DIR__.'/../vendor/autoload.php'; 25 | 26 | /* 27 | |-------------------------------------------------------------------------- 28 | | Turn On The Lights 29 | |-------------------------------------------------------------------------- 30 | | 31 | | We need to illuminate PHP development, so let us turn on the lights. 32 | | This bootstraps the framework and gets it ready for use, then it 33 | | will load up this application so that we can run it and send 34 | | the responses back to the browser and delight our users. 35 | | 36 | */ 37 | 38 | $app = require_once __DIR__.'/../bootstrap/app.php'; 39 | 40 | /* 41 | |-------------------------------------------------------------------------- 42 | | Run The Application 43 | |-------------------------------------------------------------------------- 44 | | 45 | | Once we have the application, we can handle the incoming request 46 | | through the kernel, and send the associated response back to 47 | | the client's browser allowing them to enjoy the creative 48 | | and wonderful application we have prepared for them. 49 | | 50 | */ 51 | 52 | $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); 53 | 54 | $response = $kernel->handle( 55 | $request = Illuminate\Http\Request::capture() 56 | ); 57 | 58 | $response->send(); 59 | 60 | $kernel->terminate($request, $response); 61 | -------------------------------------------------------------------------------- /resources/views/auth/passwords/email.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
Reset Password
9 | 10 |
11 | @if (session('status')) 12 |
13 | {{ session('status') }} 14 |
15 | @endif 16 | 17 |
18 | {{ csrf_field() }} 19 | 20 |
21 | 22 | 23 |
24 | 25 | 26 | @if ($errors->has('email')) 27 | 28 | {{ $errors->first('email') }} 29 | 30 | @endif 31 |
32 |
33 | 34 |
35 |
36 | 39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | @endsection 48 | -------------------------------------------------------------------------------- /src/Infrastructure/Repositories/Eloquent/User/ApplicationRepository.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @copyright Keith Mifsud 2018 11 | * 12 | * @since 1.0 13 | * @version 1.0 Initial Release 14 | */ 15 | 16 | namespace KeithMifsud\Demo\Infrastructure\Repositories\Eloquent\User; 17 | 18 | use KeithMifsud\Demo\Domain\Common\UniqueIdentifier\UniqueIdentifier; 19 | use KeithMifsud\Demo\Application\Repositories\Authentication\UserRepository; 20 | 21 | /** 22 | * Eloquent implementation of the application's user repository. 23 | * 24 | */ 25 | final class ApplicationRepository implements UserRepository 26 | { 27 | 28 | /** 29 | * @var User 30 | */ 31 | protected $model; 32 | 33 | 34 | /** 35 | * ApplicationRepository constructor. 36 | * 37 | * @param User $model 38 | */ 39 | public function __construct(User $model) 40 | { 41 | $this->model = $model; 42 | } 43 | 44 | 45 | /** 46 | * Creates a new user in repository. 47 | * 48 | * @param UniqueIdentifier $identifier 49 | * @param string $emailAddress 50 | * @param string $password 51 | * @return mixed 52 | */ 53 | public function createNewUser( 54 | UniqueIdentifier $identifier, 55 | string $emailAddress, 56 | string $password 57 | ) { 58 | 59 | return $this->model->create([ 60 | 'user_identifier' => $identifier->toString(), 61 | 'email' => $emailAddress, 62 | 'password' => $password 63 | ]); 64 | } 65 | 66 | 67 | /** 68 | * Gets a user by its unique identifier. 69 | * 70 | * @param UniqueIdentifier $identifier 71 | * @return mixed 72 | */ 73 | public function getUserByIdentifier(UniqueIdentifier $identifier) 74 | { 75 | return $this->model->where([ 76 | 'user_identifier' => $identifier->toString() 77 | ])->first(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Domain/Common/UniqueIdentifier/BaseUniqueIdentifier.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @copyright Keith Mifsud 2018 11 | * 12 | * @since 1.0 13 | * @version 1.0 Initial Release 14 | */ 15 | 16 | namespace KeithMifsud\Demo\Domain\Common\UniqueIdentifier; 17 | 18 | use Ramsey\Uuid\Uuid; 19 | 20 | /** 21 | * An extensible unique identifier. 22 | * 23 | * Serves as an adaptor to the Uuid package. 24 | */ 25 | class BaseUniqueIdentifier implements UniqueIdentifier 26 | { 27 | 28 | /** 29 | * @var mixed 30 | */ 31 | protected $identifier; 32 | 33 | 34 | /** 35 | * Generates a unique identifier. 36 | * 37 | * @return UniqueIdentifier 38 | */ 39 | public static function generate(): UniqueIdentifier 40 | { 41 | return new self(Uuid::uuid4()); 42 | } 43 | 44 | 45 | /** 46 | * Gets the identifier from a string. 47 | * 48 | * @param string $identifier 49 | * @return UniqueIdentifier 50 | */ 51 | public static function fromString(string $identifier): UniqueIdentifier 52 | { 53 | return new self(Uuid::fromString($identifier)); 54 | } 55 | 56 | 57 | /** 58 | * BaseUniqueIdentifier constructor. 59 | * 60 | * @param $identifier 61 | */ 62 | protected function __construct($identifier) 63 | { 64 | $this->identifier = $identifier; 65 | } 66 | 67 | 68 | /** 69 | * Gets the identifier as a string. 70 | * 71 | * @return string 72 | */ 73 | public function toString(): string 74 | { 75 | return $this->identifier->toString(); 76 | } 77 | 78 | 79 | /** 80 | * Determines if both identifiers 81 | * have the same value. 82 | * 83 | * @param UniqueIdentifier $otherIdentifier 84 | * @return bool 85 | */ 86 | public function sameValueAs(UniqueIdentifier $otherIdentifier): bool 87 | { 88 | return $this->toString() == $otherIdentifier->toString(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/RegisterController.php: -------------------------------------------------------------------------------- 1 | middleware('guest'); 45 | } 46 | 47 | 48 | /** 49 | * Registers and authenticates the new member. 50 | * 51 | * @param RegisterMember $request 52 | * @param RegisterMemberService $service 53 | * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector 54 | */ 55 | public function registerMember( 56 | RegisterMember $request, 57 | RegisterMemberService $service 58 | ) { 59 | $authUser = $service->execute( 60 | $request->get('email'), 61 | $request->get('password'), 62 | $request->get('first_name'), 63 | $request->get('last_name') 64 | ); 65 | 66 | $this->guard()->login($authUser); 67 | $request->session()->flash( 68 | 'alert-success', 69 | 'You have successfully registered and logged in.' 70 | ); 71 | return redirect($this->redirectPath()); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /config/filesystems.php: -------------------------------------------------------------------------------- 1 | env('FILESYSTEM_DRIVER', 'local'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Default Cloud Filesystem Disk 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Many applications store files both locally and in the cloud. For this 24 | | reason, you may specify a default "cloud" driver here. This driver 25 | | will be bound as the Cloud disk implementation in the container. 26 | | 27 | */ 28 | 29 | 'cloud' => env('FILESYSTEM_CLOUD', 's3'), 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Filesystem Disks 34 | |-------------------------------------------------------------------------- 35 | | 36 | | Here you may configure as many filesystem "disks" as you wish, and you 37 | | may even configure multiple disks of the same driver. Defaults have 38 | | been setup for each driver as an example of the required options. 39 | | 40 | | Supported Drivers: "local", "ftp", "s3", "rackspace" 41 | | 42 | */ 43 | 44 | 'disks' => [ 45 | 46 | 'local' => [ 47 | 'driver' => 'local', 48 | 'root' => storage_path('app'), 49 | ], 50 | 51 | 'public' => [ 52 | 'driver' => 'local', 53 | 'root' => storage_path('app/public'), 54 | 'url' => env('APP_URL').'/storage', 55 | 'visibility' => 'public', 56 | ], 57 | 58 | 's3' => [ 59 | 'driver' => 's3', 60 | 'key' => env('AWS_ACCESS_KEY_ID'), 61 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 62 | 'region' => env('AWS_DEFAULT_REGION'), 63 | 'bucket' => env('AWS_BUCKET'), 64 | ], 65 | 66 | ], 67 | 68 | ]; 69 | -------------------------------------------------------------------------------- /tests/Unit/Domain/Member/Address/CountryTest.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * @copyright Keith Mifsud 2018 10 | * 11 | * @since 1.0 12 | * @version 1.0 Initial Release 13 | */ 14 | 15 | namespace KeithMifsud\Demo\Tests\Unit\Domain\Member\Address; 16 | 17 | use KeithMifsud\Demo\Tests\TestCase; 18 | use KeithMifsud\Demo\Domain\Member\Address\Country; 19 | use KeithMifsud\Demo\Domain\Member\Exception\InvalidCountry; 20 | 21 | /** 22 | * Unit tests for the Country value object. 23 | * 24 | */ 25 | class CountryTest extends TestCase 26 | { 27 | 28 | /** 29 | * Tests that a country can be initialised from a valid 30 | * iso 3 code. 31 | * 32 | * @test 33 | */ 34 | public function it_can_be_instantiated_from_valid_code() 35 | { 36 | $isoCountryCode = 'GBR'; 37 | $countryName = 'United Kingdom'; 38 | 39 | $country = new Country($isoCountryCode); 40 | $this->assertInstanceOf( 41 | Country::class, 42 | $country 43 | ); 44 | $this->assertEquals( 45 | $countryName, 46 | $country->getValue() 47 | ); 48 | $this->assertEquals( 49 | $countryName, 50 | $country->toString() 51 | ); 52 | } 53 | 54 | 55 | /** 56 | * Tests that it can get the country's code. 57 | * 58 | * @test 59 | */ 60 | public function it_can_get_its_country_code() 61 | { 62 | $country = new Country('HKG'); 63 | $this->assertEquals( 64 | 'Hong Kong', 65 | $country->getValue() 66 | ); 67 | 68 | $this->assertEquals( 69 | 'HKG', 70 | $country->getCode() 71 | ); 72 | } 73 | 74 | 75 | /** 76 | * Tests that an exception is thrown when 77 | * initiated with an invalid country code. 78 | * 79 | * @test 80 | * @expectedException \KeithMifsud\Demo\Domain\Member\Exception\InvalidCountry 81 | */ 82 | public function it_throws_exception_with_an_invalid_country_code() 83 | { 84 | $invalidCountryCode = 'XXX'; 85 | $country = new Country($invalidCountryCode); 86 | } 87 | } 88 | 89 | -------------------------------------------------------------------------------- /app/Http/Kernel.php: -------------------------------------------------------------------------------- 1 | [ 31 | \KeithMifsud\Demo\Application\Http\Middleware\EncryptCookies::class, 32 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, 33 | \Illuminate\Session\Middleware\StartSession::class, 34 | // \Illuminate\Session\Middleware\AuthenticateSession::class, 35 | \Illuminate\View\Middleware\ShareErrorsFromSession::class, 36 | \KeithMifsud\Demo\Application\Http\Middleware\VerifyCsrfToken::class, 37 | \Illuminate\Routing\Middleware\SubstituteBindings::class, 38 | ], 39 | 40 | 'api' => [ 41 | 'throttle:60,1', 42 | 'bindings', 43 | ], 44 | ]; 45 | 46 | /** 47 | * The application's route middleware. 48 | * 49 | * These middleware may be assigned to groups or used individually. 50 | * 51 | * @var array 52 | */ 53 | protected $routeMiddleware = [ 54 | 'auth' => \Illuminate\Auth\Middleware\Authenticate::class, 55 | 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 56 | 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, 57 | 'can' => \Illuminate\Auth\Middleware\Authorize::class, 58 | 'guest' => \KeithMifsud\Demo\Application\Http\Middleware\RedirectIfAuthenticated::class, 59 | 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 60 | ]; 61 | } 62 | -------------------------------------------------------------------------------- /app/Services/Membership/RegisterMember.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @copyright Keith Mifsud 2018 11 | * 12 | * @since 1.0 13 | * @version 1.0 Initial Release 14 | */ 15 | 16 | namespace KeithMifsud\Demo\Application\Services\Membership; 17 | 18 | use KeithMifsud\Demo\Domain\Member\Member; 19 | use Illuminate\Contracts\Auth\Authenticatable; 20 | use KeithMifsud\Demo\Domain\Common\UniqueIdentifier\BaseUniqueIdentifier; 21 | use KeithMifsud\Demo\Application\Repositories\Membership\MemberRepository; 22 | use KeithMifsud\Demo\Application\Repositories\Authentication\UserRepository; 23 | 24 | /** 25 | * Application service for registering a new member. 26 | * 27 | * Sets up the member and user profile. 28 | * 29 | */ 30 | final class RegisterMember 31 | { 32 | 33 | 34 | /** 35 | * @var MemberRepository 36 | */ 37 | protected $memberRepository; 38 | 39 | 40 | /** 41 | * @var UserRepository 42 | */ 43 | protected $userRepository; 44 | 45 | 46 | /** 47 | * RegisterMember constructor. 48 | * 49 | * @param MemberRepository $memberApplicationRepository 50 | * @param UserRepository $userRepository 51 | */ 52 | public function __construct( 53 | MemberRepository $memberApplicationRepository, 54 | UserRepository $userRepository 55 | ) { 56 | $this->memberRepository = $memberApplicationRepository; 57 | $this->userRepository = $userRepository; 58 | } 59 | 60 | 61 | /** 62 | * Executes the service. 63 | * 64 | * Creates an Authenticatable User and Member. 65 | * Then stores them in repositories. 66 | * 67 | * @param string $emailAddress 68 | * @param string $password 69 | * @param string $firstName 70 | * @param string $lastName 71 | * @return Authenticatable 72 | */ 73 | public function execute( 74 | string $emailAddress, 75 | string $password, 76 | string $firstName, 77 | string $lastName 78 | ): Authenticatable { 79 | 80 | $identifier = BaseUniqueIdentifier::generate(); 81 | $user = $this->userRepository->createNewUser( 82 | $identifier, 83 | $emailAddress, 84 | $password 85 | ); 86 | 87 | $member = Member::register( 88 | $identifier, 89 | $firstName, 90 | $lastName 91 | ); 92 | $this->memberRepository->storeNewMember($member); 93 | 94 | return $user; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /config/queue.php: -------------------------------------------------------------------------------- 1 | env('QUEUE_DRIVER', 'sync'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Queue Connections 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may configure the connection information for each server that 26 | | is used by your application. A default configuration has been added 27 | | for each back-end shipped with Laravel. You are free to add more. 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'sync' => [ 34 | 'driver' => 'sync', 35 | ], 36 | 37 | 'database' => [ 38 | 'driver' => 'database', 39 | 'table' => 'jobs', 40 | 'queue' => 'default', 41 | 'retry_after' => 90, 42 | ], 43 | 44 | 'beanstalkd' => [ 45 | 'driver' => 'beanstalkd', 46 | 'host' => 'localhost', 47 | 'queue' => 'default', 48 | 'retry_after' => 90, 49 | ], 50 | 51 | 'sqs' => [ 52 | 'driver' => 'sqs', 53 | 'key' => env('SQS_KEY', 'your-public-key'), 54 | 'secret' => env('SQS_SECRET', 'your-secret-key'), 55 | 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), 56 | 'queue' => env('SQS_QUEUE', 'your-queue-name'), 57 | 'region' => env('SQS_REGION', 'us-east-1'), 58 | ], 59 | 60 | 'redis' => [ 61 | 'driver' => 'redis', 62 | 'connection' => 'default', 63 | 'queue' => 'default', 64 | 'retry_after' => 90, 65 | ], 66 | 67 | ], 68 | 69 | /* 70 | |-------------------------------------------------------------------------- 71 | | Failed Queue Jobs 72 | |-------------------------------------------------------------------------- 73 | | 74 | | These options configure the behavior of failed queue job logging so you 75 | | can control which database and table are used to store the jobs that 76 | | have failed. You may change them to any database / table you wish. 77 | | 78 | */ 79 | 80 | 'failed' => [ 81 | 'database' => env('DB_CONNECTION', 'mysql'), 82 | 'table' => 'failed_jobs', 83 | ], 84 | 85 | ]; 86 | -------------------------------------------------------------------------------- /resources/views/welcome.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Laravel 9 | 10 | 11 | 13 | 14 | 15 | 67 | 68 | 69 |
70 | @if (Route::has('login')) 71 | 79 | @endif 80 | 81 |
82 |
83 | Simple DDD Demo 84 |
85 | 86 | 92 |

93 | © 2018 - Keith Mifsud 94 |

95 | 96 |
97 |
98 | 99 | 100 | -------------------------------------------------------------------------------- /src/Infrastructure/Repositories/Eloquent/Member/ApplicationRepository.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @copyright Keith Mifsud 2018 11 | * 12 | * @since 1.0 13 | * @version 1.0 Initial Release 14 | */ 15 | 16 | namespace KeithMifsud\Demo\Infrastructure\Repositories\Eloquent\Member; 17 | 18 | use KeithMifsud\Demo\Domain\Member\Member; 19 | use KeithMifsud\Demo\Domain\Common\UniqueIdentifier\UniqueIdentifier; 20 | use KeithMifsud\Demo\Application\Repositories\Membership\MemberRepository; 21 | 22 | /** 23 | * Eloquent implementation of the member's 24 | * application repository. 25 | * 26 | */ 27 | final class ApplicationRepository extends Repository implements MemberRepository 28 | { 29 | 30 | /** 31 | * Gets the member. 32 | * 33 | * @param UniqueIdentifier $memberIdentifier 34 | * @return mixed 35 | */ 36 | public function getMember(UniqueIdentifier $memberIdentifier) 37 | { 38 | return $this->model->where([ 39 | 'user_identifier' => $memberIdentifier->toString() 40 | ])->first(); 41 | } 42 | 43 | 44 | /** 45 | * Stores the new member. 46 | * 47 | * @param Member $member 48 | * @return mixed 49 | */ 50 | public function storeNewMember(Member $member) 51 | { 52 | return $this->model->create([ 53 | 'user_identifier' => $member->getIdentifier()->toString(), 54 | 'first_name' => $member->getFirstName()->toString(), 55 | 'last_name' => $member->getLastName()->toString() 56 | ]); 57 | } 58 | 59 | 60 | /** 61 | * Updates the member's profile in repository. 62 | * 63 | * @param Member $member 64 | * @return mixed 65 | */ 66 | public function updateProfile(Member $member) 67 | { 68 | $profile = $this->getMember($member->getIdentifier()); 69 | 70 | if (!is_null($member->getPhoneNumber())) { 71 | $profile->international_dialling_code = $member->getPhoneNumber() 72 | ->getInternationalDialingCode()->toString(); 73 | 74 | $profile->domestic_phone_number = $member->getPhoneNumber() 75 | ->getDomesticNumber()->toString(); 76 | } 77 | 78 | if (! is_null($member->getAddress())) { 79 | $profile->street_address = $member->getAddress()->getStreetAddress() 80 | ->toString(); 81 | 82 | $profile->city = $member->getAddress()->getCity()->toString(); 83 | $profile->region = $member->getAddress()->getRegion()->toString(); 84 | $profile->country_code = $member->getAddress()->getCountry()->getCode(); 85 | $profile->country = $member->getAddress()->getCountry()->toString(); 86 | } 87 | 88 | return $profile->save(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /config/cache.php: -------------------------------------------------------------------------------- 1 | env('CACHE_DRIVER', 'file'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Cache Stores 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may define all of the cache "stores" for your application as 26 | | well as their drivers. You may even define multiple stores for the 27 | | same cache driver to group types of items stored in your caches. 28 | | 29 | */ 30 | 31 | 'stores' => [ 32 | 33 | 'apc' => [ 34 | 'driver' => 'apc', 35 | ], 36 | 37 | 'array' => [ 38 | 'driver' => 'array', 39 | ], 40 | 41 | 'database' => [ 42 | 'driver' => 'database', 43 | 'table' => 'cache', 44 | 'connection' => null, 45 | ], 46 | 47 | 'file' => [ 48 | 'driver' => 'file', 49 | 'path' => storage_path('framework/cache/data'), 50 | ], 51 | 52 | 'memcached' => [ 53 | 'driver' => 'memcached', 54 | 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), 55 | 'sasl' => [ 56 | env('MEMCACHED_USERNAME'), 57 | env('MEMCACHED_PASSWORD'), 58 | ], 59 | 'options' => [ 60 | // Memcached::OPT_CONNECT_TIMEOUT => 2000, 61 | ], 62 | 'servers' => [ 63 | [ 64 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'), 65 | 'port' => env('MEMCACHED_PORT', 11211), 66 | 'weight' => 100, 67 | ], 68 | ], 69 | ], 70 | 71 | 'redis' => [ 72 | 'driver' => 'redis', 73 | 'connection' => 'default', 74 | ], 75 | 76 | ], 77 | 78 | /* 79 | |-------------------------------------------------------------------------- 80 | | Cache Key Prefix 81 | |-------------------------------------------------------------------------- 82 | | 83 | | When utilizing a RAM based store such as APC or Memcached, there might 84 | | be other applications utilizing the same cache. So, we'll specify a 85 | | value to get prefixed to all our keys so we can avoid collisions. 86 | | 87 | */ 88 | 89 | 'prefix' => env( 90 | 'CACHE_PREFIX', 91 | str_slug(env('APP_NAME', 'laravel'), '_').'_cache' 92 | ), 93 | 94 | ]; 95 | -------------------------------------------------------------------------------- /tests/Unit/Domain/Member/PhoneNumber/PhoneNumberTest.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * @copyright Keith Mifsud 2018 10 | * 11 | * @since 1.0 12 | * @version 1.0 Initial Release 13 | */ 14 | 15 | namespace KeithMifsud\Demo\Tests\Unit\Domain\Member\PhoneNumber; 16 | 17 | use KeithMifsud\Demo\Domain\Member\Exception\InvalidPhoneNumber; 18 | use KeithMifsud\Demo\Tests\TestCase; 19 | use KeithMifsud\Demo\Domain\Member\PhoneNumber\PhoneNumber; 20 | 21 | /** 22 | * Test for the phone number. 23 | */ 24 | class PhoneNumberTest extends TestCase 25 | { 26 | 27 | 28 | /** 29 | * Tests that it can be created from a valid 30 | * pattern. 31 | * 32 | * @test 33 | */ 34 | public function it_can_be_instantiated_from_number() 35 | { 36 | $countryCode = '44'; 37 | $localNumber = '1493334010'; 38 | 39 | $phoneNumber = new PhoneNumber($countryCode, $localNumber); 40 | 41 | $this->assertEquals( 42 | '44', 43 | $phoneNumber->getInternationalDialingCode()->getValue() 44 | ); 45 | 46 | $this->assertEquals( 47 | '44', 48 | $phoneNumber->getInternationalDialingCode()->toString() 49 | ); 50 | 51 | $this->assertEquals( 52 | $localNumber, 53 | $phoneNumber->getDomesticNumber()->getValue() 54 | ); 55 | 56 | } 57 | 58 | 59 | /** 60 | * Tests that an exception is thrown when 61 | * given an invalid country code. 62 | * 63 | * @test 64 | * @expectedException \KeithMifsud\Demo\Domain\Member\Exception\InvalidPhoneNumber 65 | */ 66 | public function it_throws_exception_with_invalid_country_code() 67 | { 68 | $countryCode = 'XXX'; 69 | $localNumber = '1493334010'; 70 | 71 | $phoneNumber = new PhoneNumber($countryCode, $localNumber); 72 | } 73 | 74 | 75 | /** 76 | * Tests that an exception is thrown when domestic number 77 | * includes alphabetic characters. 78 | * 79 | * @test 80 | * @expectedException \KeithMifsud\Demo\Domain\Member\Exception\InvalidPhoneNumber 81 | */ 82 | public function it_throws_exception_with_alpha_characters() 83 | { 84 | $countryCode = 'GBR'; 85 | $localNumber = '14933b4010'; 86 | 87 | $phoneNumber = new PhoneNumber($countryCode, $localNumber); 88 | } 89 | 90 | 91 | /** 92 | * Tests that it can get a string representation 93 | * of the phone number. 94 | * 95 | * @test 96 | */ 97 | public function it_can_get_the_number_in_string_format() 98 | { 99 | 100 | $countryCode = '44'; 101 | $localNumber = '1493334010'; 102 | $phoneNumber = new PhoneNumber($countryCode, $localNumber); 103 | 104 | $this->assertEquals( 105 | '+44 1493334010', 106 | $phoneNumber->toString() 107 | ); 108 | } 109 | } 110 | 111 | -------------------------------------------------------------------------------- /resources/views/auth/login.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
Login
9 | 10 |
11 |
12 | {{ csrf_field() }} 13 | 14 |
15 | 16 | 17 |
18 | 19 | 20 | @if ($errors->has('email')) 21 | 22 | {{ $errors->first('email') }} 23 | 24 | @endif 25 |
26 |
27 | 28 |
29 | 30 | 31 |
32 | 33 | 34 | @if ($errors->has('password')) 35 | 36 | {{ $errors->first('password') }} 37 | 38 | @endif 39 |
40 |
41 | 42 |
43 |
44 |
45 | 48 |
49 |
50 |
51 | 52 |
53 |
54 | 57 | 58 | 59 | Forgot Your Password? 60 | 61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | @endsection 70 | -------------------------------------------------------------------------------- /src/Domain/Member/Address/Address.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @copyright Keith Mifsud 2018 11 | * 12 | * @since 1.0 13 | * @version 1.0 Initial Release 14 | */ 15 | 16 | namespace KeithMifsud\Demo\Domain\Member\Address; 17 | 18 | /** 19 | * The member's address. 20 | * 21 | */ 22 | final class Address 23 | { 24 | 25 | 26 | /** 27 | * @var Street 28 | */ 29 | protected $streetAddress; 30 | 31 | 32 | /** 33 | * @var City 34 | */ 35 | protected $city; 36 | 37 | 38 | /** 39 | * @var Region 40 | */ 41 | protected $region; 42 | 43 | 44 | /** 45 | * @var Country 46 | */ 47 | protected $country; 48 | 49 | 50 | /** 51 | * Address constructor. 52 | * 53 | * @param string $streetAddress 54 | * @param string $city 55 | * @param string $region 56 | * @param string $country 57 | */ 58 | public function __construct( 59 | string $streetAddress, 60 | string $city, 61 | string $region, 62 | string $country 63 | ) { 64 | $this->streetAddress = new Street($streetAddress); 65 | $this->city = new City($city); 66 | $this->region = new Region($region); 67 | $this->country = new Country($country); 68 | } 69 | 70 | 71 | /** 72 | * Gets the StreetAddress. 73 | * 74 | * @return Street 75 | */ 76 | public function getStreetAddress(): Street 77 | { 78 | return $this->streetAddress; 79 | } 80 | 81 | 82 | /** 83 | * Gets the City. 84 | * 85 | * @return City 86 | */ 87 | public function getCity(): City 88 | { 89 | return $this->city; 90 | } 91 | 92 | 93 | /** 94 | * Gets the Region. 95 | * 96 | * @return Region 97 | */ 98 | public function getRegion(): Region 99 | { 100 | return $this->region; 101 | } 102 | 103 | 104 | /** 105 | * Gets the Country. 106 | * 107 | * @return Country 108 | */ 109 | public function getCountry(): Country 110 | { 111 | return $this->country; 112 | } 113 | 114 | 115 | /** 116 | * Determines the equality of the addresses. 117 | * 118 | * @param Address $anotherAddress 119 | * @return bool 120 | */ 121 | public function sameValueAs(Address $anotherAddress): bool 122 | { 123 | $sameStreet = $this->getStreetAddress()->sameValueAs( 124 | $anotherAddress->getStreetAddress() 125 | ); 126 | 127 | $sameCity = $this->getCity()->sameValueAs( 128 | $anotherAddress->getCity() 129 | ); 130 | 131 | $sameRegion = $this->getRegion()->sameValueAs( 132 | $anotherAddress->getRegion() 133 | ); 134 | 135 | $sameCountry = $this->getCountry()->sameValueAs( 136 | $anotherAddress->getCountry() 137 | ); 138 | 139 | return ($sameStreet && $sameCity && $sameRegion && $sameCountry); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Domain Driven Design Demo 2 | 3 | A demo application with a DDD membership domain. 4 | 5 | A working version of this demo be run locally using either Docker or Vagrant. 6 | 7 | [More details...](https://keith-mifsud.me/projects/domain-driven-design-with-laravel) 8 | 9 | ## Installation 10 | 11 | Simply clone the repository: 12 | 13 | `git clone git@github.com:keithmifsud/laravel-ddd-demo.git` 14 | 15 | and install composer's dependencies: 16 | 17 | `composer install` 18 | 19 | Create a database and update a copy of the `.env.example` file: 20 | 21 | ```bash 22 | mysql -u yourusername -p 23 | 24 | ## enter your password 25 | create database databasename; 26 | exit 27 | 28 | ## copy the example file 29 | cp .env.example .env 30 | 31 | ## Fill in the details in .env 32 | 33 | ## Then migrate the database tables 34 | php artisan migrate 35 | 36 | ## Generate an application key if you haven't yet supplied one in .env 37 | php artisan key:generate 38 | ``` 39 | 40 | 41 | ## Documentation 42 | 43 | The purpose of this demo is the code base itself. However, you can still register, login and update your profile. This process is intuitive once the application is running. 44 | 45 | In this demo, you can study the separation of layers as follows: 46 | 47 | __[Domain Layer](https://github.com/keithmifsud/laravel-ddd-demo/tree/master/src/Domain)__ 48 | 49 | The Domain Layer holds the "Business Logic". In this example, we have two contexts. One is a "Common" domain which is simply a helper domain with extensible objects. Other Bounded Contexts can have the Common domain as a dependency. 50 | 51 | The main context for this demo, is the Member domain which is also tested here [Unit Tests](https://github.com/keithmifsud/laravel-ddd-demo/tree/master/tests/Unit/Domain/Member). 52 | 53 | You may also notice the application of the Repository Pattern. The Domain depends on repository interfaces and not concrete implementations. 54 | 55 | __[Infrastructure Layer](https://github.com/keithmifsud/laravel-ddd-demo/tree/master/src/Infrastructure)__ 56 | 57 | This layer handles concrete implementations related to infrastructure requirements. For the purpose of this demo, we only have Database requirements, however, in general applications you'll have other requirements such as Mailing services and external APIs. 58 | 59 | 60 | __[Application Layer](https://github.com/keithmifsud/laravel-ddd-demo/tree/master/app)__ 61 | 62 | The Application Layer is provided by the Laravel Framework. However, the Repository Pattern is also applied here. This Layer is the glue in between the Domain and the Infrastructure layers. 63 | 64 | Please also note that the HTTP Controllers do not handle any logic which is not related to the request or response. In this Demo, I've used Application Services to handle all internal processes with the Domain and the Infrastructure. 65 | 66 | ## Support 67 | 68 | If you need any help or have any comments, please submit an issue on GitHub. 69 | 70 | ## Contribution 71 | 72 | Simply fork this repository and submit a PR with your changes. 73 | 74 | ## Copyright 75 | 76 | As per the Creative Commons community, copyright is reserved to the author: 77 | 78 | Keith Mifsud 79 | 80 | ## License 81 | 82 | The Laravel framework and this demo are open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). 83 | 84 | -------------------------------------------------------------------------------- /src/Domain/Member/PhoneNumber/PhoneNumber.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @copyright Keith Mifsud 2018 11 | * 12 | * @since 1.0 13 | * @version 1.0 Initial Release 14 | */ 15 | 16 | namespace KeithMifsud\Demo\Domain\Member\PhoneNumber; 17 | 18 | use KeithMifsud\Demo\Domain\Common\ValueObject\ValueObject; 19 | use KeithMifsud\Demo\Domain\Common\ValueObject\BaseValueObject; 20 | use KeithMifsud\Demo\Domain\Member\Exception\InvalidPhoneNumber; 21 | 22 | /** 23 | * The member's phone number. 24 | */ 25 | final class PhoneNumber 26 | { 27 | /** 28 | * @var string 29 | */ 30 | protected static $prefix = '+'; 31 | 32 | 33 | /** 34 | * @var InternationalDialingCode 35 | */ 36 | protected $internationalDialingCode; 37 | 38 | 39 | /** 40 | * @var DomesticNumber 41 | */ 42 | protected $domesticNumber; 43 | 44 | 45 | /** 46 | * PhoneNumber constructor. 47 | * 48 | * @param string $internationalDialingCode 49 | * @param string $domesticPhoneNumber 50 | */ 51 | public function __construct( 52 | string $internationalDialingCode, 53 | string $domesticPhoneNumber 54 | ) { 55 | 56 | $this->internationalDialingCode = new InternationalDialingCode( 57 | $internationalDialingCode 58 | ); 59 | $this->domesticNumber = new DomesticNumber($domesticPhoneNumber); 60 | } 61 | 62 | 63 | /** 64 | * Gets the Prefix. 65 | * 66 | * @return string 67 | */ 68 | public static function getPrefix(): string 69 | { 70 | return self::$prefix; 71 | } 72 | 73 | 74 | /** 75 | * Gets the InternationalDialingCode. 76 | * 77 | * @return InternationalDialingCode 78 | */ 79 | public function getInternationalDialingCode(): InternationalDialingCode 80 | { 81 | return $this->internationalDialingCode; 82 | } 83 | 84 | 85 | /** 86 | * Gets the DomesticNumber. 87 | * 88 | * @return DomesticNumber 89 | */ 90 | public function getDomesticNumber(): DomesticNumber 91 | { 92 | return $this->domesticNumber; 93 | } 94 | 95 | 96 | /** 97 | * Gets a string representation of the phone number. 98 | * 99 | * @return string 100 | */ 101 | public function toString(): string 102 | { 103 | return self::getPrefix() . 104 | $this->getInternationalDialingCode()->toString() . 105 | ' ' . $this->getDomesticNumber()->toString(); 106 | } 107 | 108 | 109 | /** 110 | * Determines whether the numbers are equal or not. 111 | * 112 | * @param PhoneNumber $anotherPhoneNumber 113 | * @return bool 114 | */ 115 | public function sameValueAs(PhoneNumber $anotherPhoneNumber): bool 116 | { 117 | $sameInternationalDiallingCode = $this->internationalDialingCode 118 | ->sameValueAs($anotherPhoneNumber->getInternationalDialingCode()); 119 | 120 | $sameDomesticNumber = $this->getDomesticNumber()->sameValueAs( 121 | $anotherPhoneNumber->getDomesticNumber() 122 | ); 123 | 124 | return $sameInternationalDiallingCode && $sameDomesticNumber; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /resources/views/auth/passwords/reset.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
Reset Password
9 | 10 |
11 |
12 | {{ csrf_field() }} 13 | 14 | 15 | 16 |
17 | 18 | 19 |
20 | 21 | 22 | @if ($errors->has('email')) 23 | 24 | {{ $errors->first('email') }} 25 | 26 | @endif 27 |
28 |
29 | 30 |
31 | 32 | 33 |
34 | 35 | 36 | @if ($errors->has('password')) 37 | 38 | {{ $errors->first('password') }} 39 | 40 | @endif 41 |
42 |
43 | 44 |
45 | 46 |
47 | 48 | 49 | @if ($errors->has('password_confirmation')) 50 | 51 | {{ $errors->first('password_confirmation') }} 52 | 53 | @endif 54 |
55 |
56 | 57 |
58 |
59 | 62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | @endsection 71 | -------------------------------------------------------------------------------- /config/auth.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'guard' => 'web', 18 | 'passwords' => 'users', 19 | ], 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Authentication Guards 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Next, you may define every authentication guard for your application. 27 | | Of course, a great default configuration has been defined for you 28 | | here which uses session storage and the Eloquent user provider. 29 | | 30 | | All authentication drivers have a user provider. This defines how the 31 | | users are actually retrieved out of your database or other storage 32 | | mechanisms used by this application to persist your user's data. 33 | | 34 | | Supported: "session", "token" 35 | | 36 | */ 37 | 38 | 'guards' => [ 39 | 'web' => [ 40 | 'driver' => 'session', 41 | 'provider' => 'users', 42 | ], 43 | 44 | 'api' => [ 45 | 'driver' => 'token', 46 | 'provider' => 'users', 47 | ], 48 | ], 49 | 50 | /* 51 | |-------------------------------------------------------------------------- 52 | | User Providers 53 | |-------------------------------------------------------------------------- 54 | | 55 | | All authentication drivers have a user provider. This defines how the 56 | | users are actually retrieved out of your database or other storage 57 | | mechanisms used by this application to persist your user's data. 58 | | 59 | | If you have multiple user tables or models you may configure multiple 60 | | sources which represent each model / table. These sources may then 61 | | be assigned to any extra authentication guards you have defined. 62 | | 63 | | Supported: "database", "eloquent" 64 | | 65 | */ 66 | 67 | 'providers' => [ 68 | 'users' => [ 69 | 'driver' => 'eloquent', 70 | 'model' => \KeithMifsud\Demo\Infrastructure\Repositories\Eloquent\User\User::class, 71 | ], 72 | 73 | // 'users' => [ 74 | // 'driver' => 'database', 75 | // 'table' => 'users', 76 | // ], 77 | ], 78 | 79 | /* 80 | |-------------------------------------------------------------------------- 81 | | Resetting Passwords 82 | |-------------------------------------------------------------------------- 83 | | 84 | | You may specify multiple password reset configurations if you have more 85 | | than one user table or model in the application and you want to have 86 | | separate password reset settings based on the specific user types. 87 | | 88 | | The expire time is the number of minutes that the reset token should be 89 | | considered valid. This security feature keeps tokens short-lived so 90 | | they have less time to be guessed. You may change this as needed. 91 | | 92 | */ 93 | 94 | 'passwords' => [ 95 | 'users' => [ 96 | 'provider' => 'users', 97 | 'table' => 'password_resets', 98 | 'expire' => 60, 99 | ], 100 | ], 101 | 102 | ]; 103 | -------------------------------------------------------------------------------- /app/Http/Controllers/Member/ProfileController.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @copyright Keith Mifsud 2018 11 | * 12 | * @since 1.0 13 | * @version 1.0 Initial Release 14 | */ 15 | 16 | namespace KeithMifsud\Demo\Application\Http\Controllers\Member; 17 | 18 | use Illuminate\Support\Facades\Auth; 19 | use KeithMifsud\Demo\Domain\Member\Address\CountryEnum; 20 | use KeithMifsud\Demo\Application\Http\Controllers\Controller; 21 | use KeithMifsud\Demo\Domain\Member\PhoneNumber\CountryCallingCode; 22 | use KeithMifsud\Demo\Application\Http\Requests\Membership\UpdateProfile; 23 | use KeithMifsud\Demo\Domain\Common\UniqueIdentifier\BaseUniqueIdentifier; 24 | use KeithMifsud\Demo\Application\Repositories\Membership\MemberRepository; 25 | use KeithMifsud\Demo\Application\Services\Membership\UpdateProfile as UpdateProfileService; 26 | 27 | /** 28 | * Http controller for Member's profile. 29 | * 30 | */ 31 | class ProfileController extends Controller 32 | { 33 | 34 | /** 35 | * ProfileController constructor. 36 | * 37 | */ 38 | public function __construct() 39 | { 40 | $this->middleware('auth'); 41 | } 42 | 43 | 44 | /** 45 | * Shows the member's profile form. 46 | * 47 | * @param MemberRepository $memberRepository 48 | * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View 49 | */ 50 | public function showProfileForm(MemberRepository $memberRepository) 51 | { 52 | $member = $memberRepository->getMember( 53 | BaseUniqueIdentifier::fromString(Auth::user()->user_identifier) 54 | ); 55 | 56 | $internationalDiallingCodes = $this 57 | ->getAvailableInternationalDiallingCodes(); 58 | 59 | $countries = $this->getAvailableCountries(); 60 | 61 | return view( 62 | 'member.profile', 63 | compact('member', 'internationalDiallingCodes', 'countries') 64 | ); 65 | } 66 | 67 | 68 | /** 69 | * Updates the profile. 70 | * 71 | * @param UpdateProfile $request 72 | * @param UpdateProfileService $service 73 | * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector 74 | */ 75 | public function updateProfile( 76 | UpdateProfile $request, 77 | UpdateProfileService $service 78 | ) { 79 | 80 | $service->execute( 81 | $request->get('user_identifier'), 82 | $request->all() 83 | ); 84 | 85 | $request->session()->flash( 86 | 'alert-success', 87 | 'Profile was successfully updated.' 88 | ); 89 | return redirect('home'); 90 | } 91 | 92 | 93 | /** 94 | * Gets the available countries. 95 | * 96 | * @return array 97 | */ 98 | protected function getAvailableCountries(): array 99 | { 100 | $countries = []; 101 | $isoCountryCodes = CountryEnum::getNames(); 102 | foreach ($isoCountryCodes as $isoCountryCode) { 103 | $countries[$isoCountryCode] = CountryEnum 104 | ::byName($isoCountryCode)->getValue(); 105 | } 106 | asort($countries); 107 | 108 | return $countries; 109 | } 110 | 111 | 112 | /** 113 | * Gets the available international dialling codes. 114 | * 115 | * @return array 116 | */ 117 | protected function getAvailableInternationalDiallingCodes(): array 118 | { 119 | $internationalDiallingCodes = []; 120 | $isoCodes = CountryCallingCode::getNames(); 121 | foreach ($isoCodes as $isoCode) { 122 | $internationalDiallingCodes[$isoCode] = CountryCallingCode 123 | ::byName($isoCode)->getValue(); 124 | } 125 | asort($internationalDiallingCodes); 126 | $internationalDiallingCodes = array_unique($internationalDiallingCodes); 127 | 128 | return $internationalDiallingCodes; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /app/Services/Membership/UpdateProfile.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @copyright Keith Mifsud 2018 11 | * 12 | * @since 1.0 13 | * @version 1.0 Initial Release 14 | */ 15 | 16 | namespace KeithMifsud\Demo\Application\Services\Membership; 17 | 18 | use KeithMifsud\Demo\Domain\Member\Member; 19 | use KeithMifsud\Demo\Domain\Member\MemberRepository; 20 | use KeithMifsud\Demo\Domain\Common\UniqueIdentifier\BaseUniqueIdentifier; 21 | use KeithMifsud\Demo\Application\Repositories\Membership\MemberRepository as MemberApplicationRepository; 22 | 23 | /** 24 | * Application service for updating a member's profile. 25 | * 26 | */ 27 | final class UpdateProfile 28 | { 29 | 30 | /** 31 | * @var MemberRepository 32 | */ 33 | protected $memberDomainRepository; 34 | 35 | 36 | /** 37 | * @var MemberApplicationRepository 38 | */ 39 | protected $memberApplicationRepository; 40 | 41 | 42 | /** 43 | * UpdateProfile constructor. 44 | * 45 | * @param MemberRepository $memberDomainRepository 46 | * @param MemberApplicationRepository $memberApplicationRepository 47 | */ 48 | public function __construct( 49 | MemberRepository $memberDomainRepository, 50 | MemberApplicationRepository $memberApplicationRepository 51 | ) { 52 | $this->memberDomainRepository = $memberDomainRepository; 53 | $this->memberApplicationRepository = $memberApplicationRepository; 54 | } 55 | 56 | 57 | /** 58 | * Executes the service. 59 | * 60 | * Updates the member's profile and stores 61 | * in repository. 62 | * 63 | * @param string $memberIdentifier 64 | * @param array $profileData 65 | * @return void 66 | */ 67 | public function execute( 68 | string $memberIdentifier, 69 | array $profileData 70 | ): void { 71 | 72 | $member = Member::existingMember( 73 | $this->memberDomainRepository, 74 | BaseUniqueIdentifier::fromString($memberIdentifier) 75 | ); 76 | 77 | // Updates the member's phone number if one is supplied. 78 | if (($this->notEmptyOrNull($profileData['international_dialling_code'])) && 79 | ($this->notEmptyOrNull($profileData['domestic_phone_number'])) 80 | ) { 81 | if (is_null($member->getPhoneNumber())) { 82 | $member->addPhoneNumber( 83 | $profileData['international_dialling_code'], 84 | $profileData['domestic_phone_number'] 85 | ); 86 | } else { 87 | $member->changePhoneNumber( 88 | $profileData['international_dialling_code'], 89 | $profileData['domestic_phone_number'] 90 | ); 91 | } 92 | } 93 | 94 | // Updates the member's address if supplied. 95 | if (($this->notEmptyOrNull($profileData['street_address'])) && 96 | ($this->notEmptyOrNull($profileData['city'])) && 97 | ($this->notEmptyOrNull($profileData['region'])) && 98 | ($this->notEmptyOrNull($profileData['country_code'])) 99 | ) { 100 | if (is_null($member->getAddress())) { 101 | $member->addAddress( 102 | $profileData['street_address'], 103 | $profileData['city'], 104 | $profileData['region'], 105 | $profileData['country_code'] 106 | ); 107 | } else { 108 | $member->moveAddress( 109 | $profileData['street_address'], 110 | $profileData['city'], 111 | $profileData['region'], 112 | $profileData['country_code'] 113 | ); 114 | } 115 | } 116 | 117 | $this->memberApplicationRepository->updateProfile($member); 118 | } 119 | 120 | 121 | /** 122 | * Checks if the value is empty. 123 | * 124 | * @param null $value 125 | * @return bool 126 | */ 127 | private function notEmptyOrNull($value = null) 128 | { 129 | return (!is_null($value) && ($value !== '')); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /config/database.php: -------------------------------------------------------------------------------- 1 | env('DB_CONNECTION', 'mysql'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Database Connections 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here are each of the database connections setup for your application. 24 | | Of course, examples of configuring each database platform that is 25 | | supported by Laravel is shown below to make development simple. 26 | | 27 | | 28 | | All database work in Laravel is done through the PHP PDO facilities 29 | | so make sure you have the driver for your particular database of 30 | | choice installed on your machine before you begin development. 31 | | 32 | */ 33 | 34 | 'connections' => [ 35 | 36 | 'sqlite' => [ 37 | 'driver' => 'sqlite', 38 | 'database' => env('DB_DATABASE', database_path('database.sqlite')), 39 | 'prefix' => '', 40 | ], 41 | 42 | 'mysql' => [ 43 | 'driver' => 'mysql', 44 | 'host' => env('DB_HOST', '127.0.0.1'), 45 | 'port' => env('DB_PORT', '3306'), 46 | 'database' => env('DB_DATABASE', 'forge'), 47 | 'username' => env('DB_USERNAME', 'forge'), 48 | 'password' => env('DB_PASSWORD', ''), 49 | 'unix_socket' => env('DB_SOCKET', ''), 50 | 'charset' => 'utf8mb4', 51 | 'collation' => 'utf8mb4_unicode_ci', 52 | 'prefix' => '', 53 | 'strict' => true, 54 | 'engine' => null, 55 | ], 56 | 57 | 'pgsql' => [ 58 | 'driver' => 'pgsql', 59 | 'host' => env('DB_HOST', '127.0.0.1'), 60 | 'port' => env('DB_PORT', '5432'), 61 | 'database' => env('DB_DATABASE', 'forge'), 62 | 'username' => env('DB_USERNAME', 'forge'), 63 | 'password' => env('DB_PASSWORD', ''), 64 | 'charset' => 'utf8', 65 | 'prefix' => '', 66 | 'schema' => 'public', 67 | 'sslmode' => 'prefer', 68 | ], 69 | 70 | 'sqlsrv' => [ 71 | 'driver' => 'sqlsrv', 72 | 'host' => env('DB_HOST', 'localhost'), 73 | 'port' => env('DB_PORT', '1433'), 74 | 'database' => env('DB_DATABASE', 'forge'), 75 | 'username' => env('DB_USERNAME', 'forge'), 76 | 'password' => env('DB_PASSWORD', ''), 77 | 'charset' => 'utf8', 78 | 'prefix' => '', 79 | ], 80 | 81 | ], 82 | 83 | /* 84 | |-------------------------------------------------------------------------- 85 | | Migration Repository Table 86 | |-------------------------------------------------------------------------- 87 | | 88 | | This table keeps track of all the migrations that have already run for 89 | | your application. Using this information, we can determine which of 90 | | the migrations on disk haven't actually been run in the database. 91 | | 92 | */ 93 | 94 | 'migrations' => 'migrations', 95 | 96 | /* 97 | |-------------------------------------------------------------------------- 98 | | Redis Databases 99 | |-------------------------------------------------------------------------- 100 | | 101 | | Redis is an open source, fast, and advanced key-value store that also 102 | | provides a richer set of commands than a typical key-value systems 103 | | such as APC or Memcached. Laravel makes it easy to dig right in. 104 | | 105 | */ 106 | 107 | 'redis' => [ 108 | 109 | 'client' => 'predis', 110 | 111 | 'default' => [ 112 | 'host' => env('REDIS_HOST', '127.0.0.1'), 113 | 'password' => env('REDIS_PASSWORD', null), 114 | 'port' => env('REDIS_PORT', 6379), 115 | 'database' => 0, 116 | ], 117 | 118 | ], 119 | 120 | ]; 121 | -------------------------------------------------------------------------------- /config/mail.php: -------------------------------------------------------------------------------- 1 | env('MAIL_DRIVER', 'smtp'), 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | SMTP Host Address 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Here you may provide the host address of the SMTP server used by your 27 | | applications. A default option is provided that is compatible with 28 | | the Mailgun mail service which will provide reliable deliveries. 29 | | 30 | */ 31 | 32 | 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | SMTP Host Port 37 | |-------------------------------------------------------------------------- 38 | | 39 | | This is the SMTP port used by your application to deliver e-mails to 40 | | users of the application. Like the host we have set this value to 41 | | stay compatible with the Mailgun e-mail application by default. 42 | | 43 | */ 44 | 45 | 'port' => env('MAIL_PORT', 587), 46 | 47 | /* 48 | |-------------------------------------------------------------------------- 49 | | Global "From" Address 50 | |-------------------------------------------------------------------------- 51 | | 52 | | You may wish for all e-mails sent by your application to be sent from 53 | | the same address. Here, you may specify a name and address that is 54 | | used globally for all e-mails that are sent by your application. 55 | | 56 | */ 57 | 58 | 'from' => [ 59 | 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), 60 | 'name' => env('MAIL_FROM_NAME', 'Example'), 61 | ], 62 | 63 | /* 64 | |-------------------------------------------------------------------------- 65 | | E-Mail Encryption Protocol 66 | |-------------------------------------------------------------------------- 67 | | 68 | | Here you may specify the encryption protocol that should be used when 69 | | the application send e-mail messages. A sensible default using the 70 | | transport layer security protocol should provide great security. 71 | | 72 | */ 73 | 74 | 'encryption' => env('MAIL_ENCRYPTION', 'tls'), 75 | 76 | /* 77 | |-------------------------------------------------------------------------- 78 | | SMTP Server Username 79 | |-------------------------------------------------------------------------- 80 | | 81 | | If your SMTP server requires a username for authentication, you should 82 | | set it here. This will get used to authenticate with your server on 83 | | connection. You may also set the "password" value below this one. 84 | | 85 | */ 86 | 87 | 'username' => env('MAIL_USERNAME'), 88 | 89 | 'password' => env('MAIL_PASSWORD'), 90 | 91 | /* 92 | |-------------------------------------------------------------------------- 93 | | Sendmail System Path 94 | |-------------------------------------------------------------------------- 95 | | 96 | | When using the "sendmail" driver to send e-mails, we will need to know 97 | | the path to where Sendmail lives on this server. A default path has 98 | | been provided here, which will work well on most of your systems. 99 | | 100 | */ 101 | 102 | 'sendmail' => '/usr/sbin/sendmail -bs', 103 | 104 | /* 105 | |-------------------------------------------------------------------------- 106 | | Markdown Mail Settings 107 | |-------------------------------------------------------------------------- 108 | | 109 | | If you are using Markdown based email rendering, you may configure your 110 | | theme and component paths here, allowing you to customize the design 111 | | of the emails. Or, you may simply stick with the Laravel defaults! 112 | | 113 | */ 114 | 115 | 'markdown' => [ 116 | 'theme' => 'default', 117 | 118 | 'paths' => [ 119 | resource_path('views/vendor/mail'), 120 | ], 121 | ], 122 | 123 | ]; 124 | -------------------------------------------------------------------------------- /resources/views/layouts/app.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {{ config('app.name', 'Laravel') }} 12 | 13 | 14 | 15 | 16 | 17 |
18 | 92 | 93 | 94 |
95 |
96 |
97 |
98 | @foreach(['danger', 'warning', 'success', 'info'] as $message) 99 | @if(Session::has('alert-' . $message)) 100 |
101 | {{Session::get('alert-' . $message)}} 102 | × 104 |
105 | @endif 106 | @endforeach 107 |
108 |
109 |
110 |
111 | 112 | 113 | @yield('content') 114 |
115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /resources/views/auth/register.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
Register
9 | 10 |
11 |
13 | {{ csrf_field() }} 14 | 15 |
17 | 19 | 20 |
21 | 27 | 28 | @if ($errors->has('first_name')) 29 | 30 | {{ $errors->first 31 | ('first_name') 32 | }} 33 | 34 | @endif 35 |
36 |
37 |
40 | 42 | 43 |
44 | 49 | 50 | @if ($errors->has('last_name')) 51 | 52 | {{ $errors->first 53 | ('last_name') 54 | }} 55 | 56 | @endif 57 |
58 |
59 | 60 |
61 | 64 | 65 |
66 | 69 | 70 | @if ($errors->has('email')) 71 | 72 | {{ $errors->first('email') }} 73 | 74 | @endif 75 |
76 |
77 | 78 |
79 | 81 | 82 |
83 | 86 | 87 | @if ($errors->has('password')) 88 | 89 | {{ $errors->first('password') }} 90 | 91 | @endif 92 |
93 |
94 | 95 |
96 | 99 | 100 |
101 | 105 |
106 |
107 | 108 |
109 |
110 | 114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 | @endsection 123 | -------------------------------------------------------------------------------- /resources/lang/en/validation.php: -------------------------------------------------------------------------------- 1 | 'The :attribute must be accepted.', 17 | 'active_url' => 'The :attribute is not a valid URL.', 18 | 'after' => 'The :attribute must be a date after :date.', 19 | 'after_or_equal' => 'The :attribute must be a date after or equal to :date.', 20 | 'alpha' => 'The :attribute may only contain letters.', 21 | 'alpha_dash' => 'The :attribute may only contain letters, numbers, and dashes.', 22 | 'alpha_num' => 'The :attribute may only contain letters and numbers.', 23 | 'array' => 'The :attribute must be an array.', 24 | 'before' => 'The :attribute must be a date before :date.', 25 | 'before_or_equal' => 'The :attribute must be a date before or equal to :date.', 26 | 'between' => [ 27 | 'numeric' => 'The :attribute must be between :min and :max.', 28 | 'file' => 'The :attribute must be between :min and :max kilobytes.', 29 | 'string' => 'The :attribute must be between :min and :max characters.', 30 | 'array' => 'The :attribute must have between :min and :max items.', 31 | ], 32 | 'boolean' => 'The :attribute field must be true or false.', 33 | 'confirmed' => 'The :attribute confirmation does not match.', 34 | 'date' => 'The :attribute is not a valid date.', 35 | 'date_format' => 'The :attribute does not match the format :format.', 36 | 'different' => 'The :attribute and :other must be different.', 37 | 'digits' => 'The :attribute must be :digits digits.', 38 | 'digits_between' => 'The :attribute must be between :min and :max digits.', 39 | 'dimensions' => 'The :attribute has invalid image dimensions.', 40 | 'distinct' => 'The :attribute field has a duplicate value.', 41 | 'email' => 'The :attribute must be a valid email address.', 42 | 'exists' => 'The selected :attribute is invalid.', 43 | 'file' => 'The :attribute must be a file.', 44 | 'filled' => 'The :attribute field must have a value.', 45 | 'image' => 'The :attribute must be an image.', 46 | 'in' => 'The selected :attribute is invalid.', 47 | 'in_array' => 'The :attribute field does not exist in :other.', 48 | 'integer' => 'The :attribute must be an integer.', 49 | 'ip' => 'The :attribute must be a valid IP address.', 50 | 'ipv4' => 'The :attribute must be a valid IPv4 address.', 51 | 'ipv6' => 'The :attribute must be a valid IPv6 address.', 52 | 'json' => 'The :attribute must be a valid JSON string.', 53 | 'max' => [ 54 | 'numeric' => 'The :attribute may not be greater than :max.', 55 | 'file' => 'The :attribute may not be greater than :max kilobytes.', 56 | 'string' => 'The :attribute may not be greater than :max characters.', 57 | 'array' => 'The :attribute may not have more than :max items.', 58 | ], 59 | 'mimes' => 'The :attribute must be a file of type: :values.', 60 | 'mimetypes' => 'The :attribute must be a file of type: :values.', 61 | 'min' => [ 62 | 'numeric' => 'The :attribute must be at least :min.', 63 | 'file' => 'The :attribute must be at least :min kilobytes.', 64 | 'string' => 'The :attribute must be at least :min characters.', 65 | 'array' => 'The :attribute must have at least :min items.', 66 | ], 67 | 'not_in' => 'The selected :attribute is invalid.', 68 | 'numeric' => 'The :attribute must be a number.', 69 | 'present' => 'The :attribute field must be present.', 70 | 'regex' => 'The :attribute format is invalid.', 71 | 'required' => 'The :attribute field is required.', 72 | 'required_if' => 'The :attribute field is required when :other is :value.', 73 | 'required_unless' => 'The :attribute field is required unless :other is in :values.', 74 | 'required_with' => 'The :attribute field is required when :values is present.', 75 | 'required_with_all' => 'The :attribute field is required when :values is present.', 76 | 'required_without' => 'The :attribute field is required when :values is not present.', 77 | 'required_without_all' => 'The :attribute field is required when none of :values are present.', 78 | 'same' => 'The :attribute and :other must match.', 79 | 'size' => [ 80 | 'numeric' => 'The :attribute must be :size.', 81 | 'file' => 'The :attribute must be :size kilobytes.', 82 | 'string' => 'The :attribute must be :size characters.', 83 | 'array' => 'The :attribute must contain :size items.', 84 | ], 85 | 'string' => 'The :attribute must be a string.', 86 | 'timezone' => 'The :attribute must be a valid zone.', 87 | 'unique' => 'The :attribute has already been taken.', 88 | 'uploaded' => 'The :attribute failed to upload.', 89 | 'url' => 'The :attribute format is invalid.', 90 | 91 | /* 92 | |-------------------------------------------------------------------------- 93 | | Custom Validation Language Lines 94 | |-------------------------------------------------------------------------- 95 | | 96 | | Here you may specify custom validation messages for attributes using the 97 | | convention "attribute.rule" to name the lines. This makes it quick to 98 | | specify a specific custom language line for a given attribute rule. 99 | | 100 | */ 101 | 102 | 'custom' => [ 103 | 'attribute-name' => [ 104 | 'rule-name' => 'custom-message', 105 | ], 106 | ], 107 | 108 | /* 109 | |-------------------------------------------------------------------------- 110 | | Custom Validation Attributes 111 | |-------------------------------------------------------------------------- 112 | | 113 | | The following language lines are used to swap attribute place-holders 114 | | with something more reader friendly such as E-Mail Address instead 115 | | of "email". This simply helps us make messages a little cleaner. 116 | | 117 | */ 118 | 119 | 'attributes' => [], 120 | 121 | ]; 122 | -------------------------------------------------------------------------------- /config/session.php: -------------------------------------------------------------------------------- 1 | env('SESSION_DRIVER', 'file'), 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Session Lifetime 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Here you may specify the number of minutes that you wish the session 27 | | to be allowed to remain idle before it expires. If you want them 28 | | to immediately expire on the browser closing, set that option. 29 | | 30 | */ 31 | 32 | 'lifetime' => env('SESSION_LIFETIME', 120), 33 | 34 | 'expire_on_close' => false, 35 | 36 | /* 37 | |-------------------------------------------------------------------------- 38 | | Session Encryption 39 | |-------------------------------------------------------------------------- 40 | | 41 | | This option allows you to easily specify that all of your session data 42 | | should be encrypted before it is stored. All encryption will be run 43 | | automatically by Laravel and you can use the Session like normal. 44 | | 45 | */ 46 | 47 | 'encrypt' => false, 48 | 49 | /* 50 | |-------------------------------------------------------------------------- 51 | | Session File Location 52 | |-------------------------------------------------------------------------- 53 | | 54 | | When using the native session driver, we need a location where session 55 | | files may be stored. A default has been set for you but a different 56 | | location may be specified. This is only needed for file sessions. 57 | | 58 | */ 59 | 60 | 'files' => storage_path('framework/sessions'), 61 | 62 | /* 63 | |-------------------------------------------------------------------------- 64 | | Session Database Connection 65 | |-------------------------------------------------------------------------- 66 | | 67 | | When using the "database" or "redis" session drivers, you may specify a 68 | | connection that should be used to manage these sessions. This should 69 | | correspond to a connection in your database configuration options. 70 | | 71 | */ 72 | 73 | 'connection' => null, 74 | 75 | /* 76 | |-------------------------------------------------------------------------- 77 | | Session Database Table 78 | |-------------------------------------------------------------------------- 79 | | 80 | | When using the "database" session driver, you may specify the table we 81 | | should use to manage the sessions. Of course, a sensible default is 82 | | provided for you; however, you are free to change this as needed. 83 | | 84 | */ 85 | 86 | 'table' => 'sessions', 87 | 88 | /* 89 | |-------------------------------------------------------------------------- 90 | | Session Cache Store 91 | |-------------------------------------------------------------------------- 92 | | 93 | | When using the "apc" or "memcached" session drivers, you may specify a 94 | | cache store that should be used for these sessions. This value must 95 | | correspond with one of the application's configured cache stores. 96 | | 97 | */ 98 | 99 | 'store' => null, 100 | 101 | /* 102 | |-------------------------------------------------------------------------- 103 | | Session Sweeping Lottery 104 | |-------------------------------------------------------------------------- 105 | | 106 | | Some session drivers must manually sweep their storage location to get 107 | | rid of old sessions from storage. Here are the chances that it will 108 | | happen on a given request. By default, the odds are 2 out of 100. 109 | | 110 | */ 111 | 112 | 'lottery' => [2, 100], 113 | 114 | /* 115 | |-------------------------------------------------------------------------- 116 | | Session Cookie Name 117 | |-------------------------------------------------------------------------- 118 | | 119 | | Here you may change the name of the cookie used to identify a session 120 | | instance by ID. The name specified here will get used every time a 121 | | new session cookie is created by the framework for every driver. 122 | | 123 | */ 124 | 125 | 'cookie' => env( 126 | 'SESSION_COOKIE', 127 | str_slug(env('APP_NAME', 'laravel'), '_').'_session' 128 | ), 129 | 130 | /* 131 | |-------------------------------------------------------------------------- 132 | | Session Cookie Path 133 | |-------------------------------------------------------------------------- 134 | | 135 | | The session cookie path determines the path for which the cookie will 136 | | be regarded as available. Typically, this will be the root path of 137 | | your application but you are free to change this when necessary. 138 | | 139 | */ 140 | 141 | 'path' => '/', 142 | 143 | /* 144 | |-------------------------------------------------------------------------- 145 | | Session Cookie Domain 146 | |-------------------------------------------------------------------------- 147 | | 148 | | Here you may change the domain of the cookie used to identify a session 149 | | in your application. This will determine which domains the cookie is 150 | | available to in your application. A sensible default has been set. 151 | | 152 | */ 153 | 154 | 'domain' => env('SESSION_DOMAIN', null), 155 | 156 | /* 157 | |-------------------------------------------------------------------------- 158 | | HTTPS Only Cookies 159 | |-------------------------------------------------------------------------- 160 | | 161 | | By setting this option to true, session cookies will only be sent back 162 | | to the server if the browser has a HTTPS connection. This will keep 163 | | the cookie from being sent to you if it can not be done securely. 164 | | 165 | */ 166 | 167 | 'secure' => env('SESSION_SECURE_COOKIE', false), 168 | 169 | /* 170 | |-------------------------------------------------------------------------- 171 | | HTTP Access Only 172 | |-------------------------------------------------------------------------- 173 | | 174 | | Setting this value to true will prevent JavaScript from accessing the 175 | | value of the cookie and the cookie will only be accessible through 176 | | the HTTP protocol. You are free to modify this option if needed. 177 | | 178 | */ 179 | 180 | 'http_only' => true, 181 | 182 | /* 183 | |-------------------------------------------------------------------------- 184 | | Same-Site Cookies 185 | |-------------------------------------------------------------------------- 186 | | 187 | | This option determines how your cookies behave when cross-site requests 188 | | take place, and can be used to mitigate CSRF attacks. By default, we 189 | | do not enable this as other CSRF protection services are in place. 190 | | 191 | | Supported: "lax", "strict" 192 | | 193 | */ 194 | 195 | 'same_site' => null, 196 | 197 | ]; 198 | -------------------------------------------------------------------------------- /src/Domain/Member/Member.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @copyright Keith Mifsud 2018 11 | * 12 | * @since 1.0 13 | * @version 1.0 Initial Release 14 | */ 15 | 16 | namespace KeithMifsud\Demo\Domain\Member; 17 | 18 | use KeithMifsud\Demo\Domain\Member\Address\Address; 19 | use KeithMifsud\Demo\Domain\Member\PhoneNumber\PhoneNumber; 20 | use KeithMifsud\Demo\Domain\Common\UniqueIdentifier\UniqueIdentifier; 21 | 22 | /** 23 | * A registered member. 24 | * 25 | */ 26 | final class Member 27 | { 28 | 29 | /** 30 | * @var UniqueIdentifier 31 | */ 32 | protected $identifier; 33 | 34 | 35 | /** 36 | * @var FirstName 37 | */ 38 | protected $firstName; 39 | 40 | 41 | /** 42 | * @var LastName 43 | */ 44 | protected $lastName; 45 | 46 | 47 | /** 48 | * @var PhoneNumber 49 | */ 50 | protected $phoneNumber; 51 | 52 | 53 | /** 54 | * @var Address 55 | */ 56 | protected $address; 57 | 58 | 59 | /** 60 | * Registers a new member. 61 | * 62 | * @param UniqueIdentifier $memberIdentifier 63 | * @param string $firstName 64 | * @param string $lastName 65 | * @return static 66 | */ 67 | public static function register( 68 | UniqueIdentifier $memberIdentifier, 69 | string $firstName, 70 | string $lastName 71 | ) { 72 | $member = new self( 73 | $memberIdentifier, 74 | new FirstName($firstName), 75 | new LastName($lastName) 76 | ); 77 | return $member; 78 | } 79 | 80 | 81 | /** 82 | * Existing member factory. 83 | * 84 | * @param MemberRepository $memberRepository 85 | * @param UniqueIdentifier $memberIdentifier 86 | * @return Member 87 | */ 88 | public static function existingMember( 89 | MemberRepository $memberRepository, 90 | UniqueIdentifier $memberIdentifier 91 | ) { 92 | $profile = $memberRepository->getExistingMemberProfile( 93 | $memberIdentifier 94 | ); 95 | 96 | $firstName = new FirstName($profile->first_name); 97 | $lastName = new LastName($profile->last_name); 98 | $phoneNumber = null; 99 | $address = null; 100 | 101 | if ((!is_null($profile->international_dialling_code)) && 102 | (!is_null($profile->domestic_phone_number)) 103 | ) { 104 | $phoneNumber = new PhoneNumber( 105 | $profile->international_dialling_code, 106 | $profile->domestic_phone_number 107 | ); 108 | } 109 | 110 | if ((!is_null($profile->street_address)) && 111 | (!is_null($profile->city)) && 112 | (!is_null($profile->region)) && 113 | (!is_null($profile->country_code)) 114 | ) { 115 | $address = new Address( 116 | $profile->street_address, 117 | $profile->city, 118 | $profile->region, 119 | $profile->country_code 120 | ); 121 | } 122 | 123 | return new self( 124 | $memberIdentifier, 125 | $firstName, 126 | $lastName, 127 | $phoneNumber, 128 | $address 129 | ); 130 | } 131 | 132 | 133 | /** 134 | * Member constructor. 135 | * 136 | * @param UniqueIdentifier $identifier 137 | * @param FirstName $firstName 138 | * @param LastName $lastName 139 | * @param PhoneNumber|null $phoneNumber 140 | * @param Address|null $address 141 | */ 142 | protected function __construct( 143 | UniqueIdentifier $identifier, 144 | FirstName $firstName, 145 | LastName $lastName, 146 | PhoneNumber $phoneNumber = null, 147 | Address $address = null 148 | ) { 149 | $this->setIdentifier($identifier); 150 | $this->setFirstName($firstName); 151 | $this->setLastName($lastName); 152 | 153 | if (!is_null($phoneNumber)) { 154 | $this->setPhoneNumber($phoneNumber); 155 | } 156 | if (!is_null($address)) { 157 | $this->setAddress($address); 158 | } 159 | } 160 | 161 | 162 | /** 163 | * Adds a phone number to the profile 164 | * if there isn't one already attached. 165 | * 166 | * @param string $internationalDialingCode 167 | * @param string $domesticNumber 168 | */ 169 | public function addPhoneNumber( 170 | string $internationalDialingCode, 171 | string $domesticNumber 172 | ) { 173 | if (!isset($this->phoneNumber)) { 174 | $this->setPhoneNumber( 175 | new PhoneNumber($internationalDialingCode, $domesticNumber) 176 | ); 177 | } 178 | } 179 | 180 | 181 | /** 182 | * Adds the address to the profile 183 | * if one is not present. 184 | * 185 | * @param string $streetAddress 186 | * @param string $city 187 | * @param string $regionOrState 188 | * @param string $countryCode 189 | */ 190 | public function addAddress( 191 | string $streetAddress, 192 | string $city, 193 | string $regionOrState, 194 | string $countryCode 195 | ) { 196 | 197 | if (!isset($this->address)) { 198 | $this->setAddress(new Address( 199 | $streetAddress, 200 | $city, 201 | $regionOrState, 202 | $countryCode 203 | )); 204 | } 205 | } 206 | 207 | 208 | /** 209 | * Changes the phone number if one exists already 210 | * and if it different from the original number. 211 | * 212 | * @param string $newInternationalDiallingCode 213 | * @param string $newDomesticPhoneNumber 214 | */ 215 | public function changePhoneNumber( 216 | string $newInternationalDiallingCode, 217 | string $newDomesticPhoneNumber 218 | ) { 219 | 220 | if (isset($this->phoneNumber)) { 221 | $newPhoneNumber = new PhoneNumber( 222 | $newInternationalDiallingCode, 223 | $newDomesticPhoneNumber 224 | ); 225 | 226 | if (!$this->getPhoneNumber()->sameValueAs($newPhoneNumber)) { 227 | $this->setPhoneNumber($newPhoneNumber); 228 | } 229 | } 230 | } 231 | 232 | 233 | /** 234 | * Moves address. 235 | * 236 | * @param string $newStreetAddress 237 | * @param string $newCity 238 | * @param string $newRegionOrState 239 | * @param string $newCountryCode 240 | */ 241 | public function moveAddress( 242 | string $newStreetAddress, 243 | string $newCity, 244 | string $newRegionOrState, 245 | string $newCountryCode 246 | ) { 247 | 248 | if (isset($this->address)) { 249 | $newAddress = new Address( 250 | $newStreetAddress, 251 | $newCity, 252 | $newRegionOrState, 253 | $newCountryCode 254 | ); 255 | 256 | if (!$this->getAddress()->sameValueAs($newAddress)) { 257 | $this->setAddress($newAddress); 258 | } 259 | } 260 | } 261 | 262 | 263 | /** 264 | * Gets the Identifier. 265 | * 266 | * @return UniqueIdentifier 267 | */ 268 | public function getIdentifier(): UniqueIdentifier 269 | { 270 | return $this->identifier; 271 | } 272 | 273 | 274 | /** 275 | * Gets the FirstName. 276 | * 277 | * @return FirstName 278 | */ 279 | public function getFirstName(): FirstName 280 | { 281 | return $this->firstName; 282 | } 283 | 284 | 285 | /** 286 | * Gets the LastName. 287 | * 288 | * @return LastName 289 | */ 290 | public function getLastName(): LastName 291 | { 292 | return $this->lastName; 293 | } 294 | 295 | 296 | /** 297 | * Gets the PhoneNumber. 298 | * 299 | * @return PhoneNumber|null 300 | */ 301 | public function getPhoneNumber(): ?PhoneNumber 302 | { 303 | return $this->phoneNumber; 304 | } 305 | 306 | 307 | /** 308 | * Gets the Address. 309 | * 310 | * @return Address|null 311 | */ 312 | public function getAddress(): ?Address 313 | { 314 | return $this->address; 315 | } 316 | 317 | 318 | /** 319 | * Sets the Identifier. 320 | * 321 | * @param UniqueIdentifier $identifier 322 | */ 323 | protected function setIdentifier(UniqueIdentifier $identifier) 324 | { 325 | $this->identifier = $identifier; 326 | } 327 | 328 | 329 | /** 330 | * Sets the FirstName. 331 | * 332 | * @param FirstName $firstName 333 | */ 334 | protected function setFirstName(FirstName $firstName) 335 | { 336 | $this->firstName = $firstName; 337 | } 338 | 339 | 340 | /** 341 | * Sets the LastName. 342 | * 343 | * @param LastName $lastName 344 | */ 345 | protected function setLastName(LastName $lastName) 346 | { 347 | $this->lastName = $lastName; 348 | } 349 | 350 | 351 | /** 352 | * Sets the PhoneNumber. 353 | * 354 | * @param PhoneNumber $phoneNumber 355 | */ 356 | protected function setPhoneNumber(PhoneNumber $phoneNumber) 357 | { 358 | $this->phoneNumber = $phoneNumber; 359 | } 360 | 361 | 362 | /** 363 | * Sets the Address. 364 | * 365 | * @param Address $address 366 | */ 367 | protected function setAddress(Address $address) 368 | { 369 | $this->address = $address; 370 | } 371 | 372 | } 373 | -------------------------------------------------------------------------------- /src/Domain/Member/PhoneNumber/CountryCallingCode.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @copyright Keith Mifsud 2018 11 | * 12 | * @since 1.0 13 | * @version 1.0 Initial Release 14 | */ 15 | 16 | namespace KeithMifsud\Demo\Domain\Member\PhoneNumber; 17 | 18 | use KeithMifsud\Demo\Domain\Common\ValueObject\Enum; 19 | use KeithMifsud\Demo\Domain\Common\ValueObject\BaseEnum; 20 | 21 | /** 22 | * An extensible country calling code enumerator. 23 | * 24 | */ 25 | class CountryCallingCode extends BaseEnum implements Enum 26 | { 27 | 28 | 29 | const AFG = '93'; 30 | 31 | 32 | const ALB = '355'; 33 | 34 | 35 | const ASM = '1684'; 36 | 37 | 38 | const AND = '376'; 39 | 40 | 41 | const AGO = '244'; 42 | 43 | 44 | const AIA = '1264'; 45 | 46 | 47 | const ATA = '672'; 48 | 49 | 50 | const ATG = '1268'; 51 | 52 | 53 | const ARG = '54'; 54 | 55 | 56 | const ARM = '374'; 57 | 58 | 59 | const ABW = '297'; 60 | 61 | 62 | const AUS = '61'; 63 | 64 | 65 | const AUT = '43'; 66 | 67 | 68 | const AZE = '994'; 69 | 70 | 71 | const BHS = '1242'; 72 | 73 | 74 | const BHR = '973'; 75 | 76 | 77 | const BGD = '880'; 78 | 79 | 80 | const BRB = '1246'; 81 | 82 | 83 | const BLR = '375'; 84 | 85 | 86 | const BEL = '32'; 87 | 88 | 89 | const BLZ = '501'; 90 | 91 | 92 | const BEN = '229'; 93 | 94 | 95 | const BMU = '1441'; 96 | 97 | 98 | const BTN = '975'; 99 | 100 | 101 | const BOL = '591'; 102 | 103 | 104 | const BIH = '387'; 105 | 106 | 107 | const BWA = '267'; 108 | 109 | 110 | const BRA = '55'; 111 | 112 | 113 | const BRN = '673'; 114 | 115 | 116 | const BGR = '359'; 117 | 118 | 119 | const BFA = '226'; 120 | 121 | 122 | const BDI = '257'; 123 | 124 | 125 | const KHM = '855'; 126 | 127 | 128 | const CMR = '237'; 129 | 130 | 131 | const CAN = '1'; 132 | 133 | 134 | const CPV = '238'; 135 | 136 | 137 | const CYM = '1345'; 138 | 139 | 140 | const CAF = '236'; 141 | 142 | 143 | const TCD = '235'; 144 | 145 | 146 | const CHL = '56'; 147 | 148 | 149 | const CHN = '86'; 150 | 151 | 152 | const HKG = '852'; 153 | 154 | 155 | const MAC = '835'; 156 | 157 | 158 | const CXR = '61'; 159 | 160 | 161 | const CCK = '61'; 162 | 163 | 164 | const COL = '57'; 165 | 166 | 167 | const COM = '269'; 168 | 169 | 170 | const COG = '242'; 171 | 172 | 173 | const COD = '243'; 174 | 175 | 176 | const COK = '682'; 177 | 178 | 179 | const CRI = '506'; 180 | 181 | 182 | const CIV = '225'; 183 | 184 | 185 | const HRV = '385'; 186 | 187 | 188 | const CUB = '53'; 189 | 190 | 191 | const CYP = '357'; 192 | 193 | 194 | const CZE = '420'; 195 | 196 | 197 | const DNK = '45'; 198 | 199 | 200 | const DKI = '253'; 201 | 202 | 203 | const DMA = '1767'; 204 | 205 | 206 | const DOM = '1809'; 207 | 208 | 209 | const ECU = '593'; 210 | 211 | 212 | const EGY = '20'; 213 | 214 | 215 | const SLV = '503'; 216 | 217 | 218 | const GNQ = '240'; 219 | 220 | 221 | const ERI = '291'; 222 | 223 | 224 | const EST = '372'; 225 | 226 | 227 | const ETH = '251'; 228 | 229 | 230 | const FLK = '500'; 231 | 232 | 233 | const FRO = '298'; 234 | 235 | 236 | const FJI = '679'; 237 | 238 | 239 | const FIN = '358'; 240 | 241 | 242 | const FRA = '33'; 243 | 244 | 245 | const GUF = '675'; 246 | 247 | 248 | const PYF = '689'; 249 | 250 | 251 | const GAB = '241'; 252 | 253 | 254 | const GMB = '220'; 255 | 256 | 257 | const GEO = '995'; 258 | 259 | 260 | const DEU = '49'; 261 | 262 | 263 | const GHA = '233'; 264 | 265 | 266 | const GIB = '350'; 267 | 268 | 269 | const GRC = '30'; 270 | 271 | 272 | const GRL = '299'; 273 | 274 | 275 | const GRD = '1473'; 276 | 277 | 278 | const GUM = '1671'; 279 | 280 | 281 | const GTM = '502'; 282 | 283 | 284 | const GIN = '224'; 285 | 286 | 287 | const GNB = '245'; 288 | 289 | 290 | const GUY = '592'; 291 | 292 | 293 | const HTI = '509'; 294 | 295 | 296 | const VAT = '39'; 297 | 298 | 299 | const HND = '504'; 300 | 301 | 302 | const HUN = '36'; 303 | 304 | 305 | const ISL = '354'; 306 | 307 | 308 | const IND = '91'; 309 | 310 | 311 | const IDN = '62'; 312 | 313 | 314 | const IRN = "98"; 315 | 316 | 317 | const IRQ = '964'; 318 | 319 | 320 | const IRL = '353'; 321 | 322 | 323 | const IMN = '44'; 324 | 325 | 326 | const ISR = '972'; 327 | 328 | 329 | const ITA = '39'; 330 | 331 | 332 | const JAM = '1876'; 333 | 334 | 335 | const JPN = '81'; 336 | 337 | 338 | const JOR = '962'; 339 | 340 | 341 | const KAZ = '7'; 342 | 343 | 344 | const KEN = '254'; 345 | 346 | 347 | const KIR = '686'; 348 | 349 | 350 | const PRK = '850'; 351 | 352 | 353 | const KOR = '82'; 354 | 355 | 356 | const kwt = '965'; 357 | 358 | 359 | const KGZ = '996'; 360 | 361 | 362 | const LAO = '856'; 363 | 364 | 365 | const LVA = '371'; 366 | 367 | 368 | const LBN = '961'; 369 | 370 | 371 | const LSO = '266'; 372 | 373 | 374 | const LBR = '231'; 375 | 376 | 377 | const LBY = '218'; 378 | 379 | 380 | const LIE = '423'; 381 | 382 | 383 | const LTU = '37'; 384 | 385 | 386 | const LUX = '352'; 387 | 388 | 389 | const MKD = '389'; 390 | 391 | 392 | const MDG = '261'; 393 | 394 | 395 | const MWI = '265'; 396 | 397 | 398 | const MYS = '60'; 399 | 400 | 401 | const MDV = '960'; 402 | 403 | 404 | const MLI = '223'; 405 | 406 | 407 | const MLT = '356'; 408 | 409 | 410 | const MHL = '692'; 411 | 412 | 413 | const MRT = '222'; 414 | 415 | 416 | const MUS = '230'; 417 | 418 | 419 | const MYT = '262'; 420 | 421 | 422 | const MEX = '52'; 423 | 424 | 425 | const FSM = '691'; 426 | 427 | 428 | const MDA = '373'; 429 | 430 | 431 | const MCO = '377'; 432 | 433 | 434 | const MNG = '976'; 435 | 436 | 437 | const MNE = '382'; 438 | 439 | 440 | const MSR = '1664'; 441 | 442 | 443 | const MAR = '212'; 444 | 445 | 446 | const MOZ = '258'; 447 | 448 | 449 | const MMR = '95'; 450 | 451 | 452 | const NAM = '264'; 453 | 454 | 455 | const NRU = '674'; 456 | 457 | 458 | const NPL = '977'; 459 | 460 | 461 | const NLD = '31'; 462 | 463 | 464 | const ANT = '599'; 465 | 466 | 467 | const NCL = '687'; 468 | 469 | 470 | const NZL = '64'; 471 | 472 | 473 | const NIC = '505'; 474 | 475 | 476 | const NER = '227'; 477 | 478 | 479 | const NGA = '234'; 480 | 481 | 482 | const NIU = '683'; 483 | 484 | 485 | const MNP = '1670'; 486 | 487 | 488 | const NOR = '47'; 489 | 490 | 491 | const OMN = '968'; 492 | 493 | 494 | const PAK = '92'; 495 | 496 | 497 | const PLW = '680'; 498 | 499 | 500 | const PAN = '507'; 501 | 502 | 503 | const PNG = '675'; 504 | 505 | 506 | const PRY = '595'; 507 | 508 | 509 | const PER = '51'; 510 | 511 | 512 | const PHL = '63'; 513 | 514 | 515 | const PCN = '870'; 516 | 517 | 518 | const POL = '48'; 519 | 520 | 521 | const PRT = '351'; 522 | 523 | 524 | const PRI = '1'; 525 | 526 | 527 | const QAT = '974'; 528 | 529 | 530 | const ROU = '40'; 531 | 532 | 533 | const RUS = '7'; 534 | 535 | 536 | const RWA = '250'; 537 | 538 | 539 | const BLM = '590'; 540 | 541 | 542 | const SHN = '290'; 543 | 544 | 545 | const KNA = '1869'; 546 | 547 | 548 | const LCA = '1758'; 549 | 550 | 551 | const SPM = '508'; 552 | 553 | 554 | const VCT = '1784'; 555 | 556 | 557 | const WSM = '685'; 558 | 559 | 560 | const SMR = '378'; 561 | 562 | 563 | const STP = '239'; 564 | 565 | 566 | const SAU = '966'; 567 | 568 | 569 | const SEN = '221'; 570 | 571 | 572 | const SRB = '381'; 573 | 574 | 575 | const SYC = '248'; 576 | 577 | 578 | const SLE = '232'; 579 | 580 | 581 | const SGP = '65'; 582 | 583 | 584 | const SVK = '421'; 585 | 586 | 587 | const SVN = '386'; 588 | 589 | 590 | const SLB = '677'; 591 | 592 | 593 | const SOM = '252'; 594 | 595 | 596 | const ZAF = '27'; 597 | 598 | 599 | const ESP = '34'; 600 | 601 | 602 | const LKA = '94'; 603 | 604 | 605 | const SDN = '249'; 606 | 607 | 608 | const SUR = '597'; 609 | 610 | 611 | const SWZ = '268'; 612 | 613 | 614 | const SWE = '46'; 615 | 616 | 617 | const CHE = '41'; 618 | 619 | 620 | const SYR = '963'; 621 | 622 | 623 | const TWN = '886'; 624 | 625 | 626 | const TJK = '992'; 627 | 628 | 629 | const TZA = '255'; 630 | 631 | 632 | const THA = '66'; 633 | 634 | 635 | const TLS = '670'; 636 | 637 | 638 | const TGO = '228'; 639 | 640 | 641 | const TKL = '690'; 642 | 643 | 644 | const TON = '676'; 645 | 646 | 647 | const TTO = '1868'; 648 | 649 | 650 | const TUN = '216'; 651 | 652 | 653 | const TUR = '90'; 654 | 655 | 656 | const TKM = '993'; 657 | 658 | 659 | const TCA = '1649'; 660 | 661 | 662 | const TUV = '688'; 663 | 664 | 665 | const UGA = '256'; 666 | 667 | 668 | const UKR = '380'; 669 | 670 | 671 | const ARE = '971'; 672 | 673 | 674 | const GBR = '44'; 675 | 676 | 677 | const USA = '1'; 678 | 679 | 680 | const URY = '598'; 681 | 682 | 683 | const UZB = '998'; 684 | 685 | 686 | const VUT = '678'; 687 | 688 | 689 | const VEN = '58'; 690 | 691 | 692 | const VNM = '84'; 693 | 694 | 695 | const VIR = '1340'; 696 | 697 | 698 | const WLF = '681'; 699 | 700 | 701 | const YEM = '967'; 702 | 703 | 704 | const ZMB = '260'; 705 | 706 | 707 | const ZWE = '263'; 708 | 709 | 710 | const VGB = '1284'; 711 | 712 | } 713 | -------------------------------------------------------------------------------- /resources/views/member/profile.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
{{$member->first_name}}'s 9 | Profile 10 |
11 |
12 |
14 | 15 | {{ csrf_field() }} 16 | 17 | 20 | 21 |
23 | 27 |
28 | 43 | @if($errors->has 44 | ('international_dialling_code')) 45 | 46 | {{$errors->first('international_dialling_code') 47 | }} 48 | 49 | @endif 50 |
51 | 52 |
53 | 59 | @if($errors->has('domestic_phone_number')) 60 | 61 | {{$errors->first('domestic_phone_number')}} 62 | 63 | @endif 64 |
65 |
66 | 67 |
69 | 73 |
74 | 79 | @if($errors->has('street_address')) 80 | 81 | {{ $errors->first('street_address') 82 | }} 83 | 84 | @endif 85 |
86 |
87 | 88 |
90 | 94 |
95 | 100 | @if($errors->has('city')) 101 | 102 | {{ $errors->first('city') 103 | }} 104 | 105 | @endif 106 |
107 |
108 | 109 |
111 | 115 |
116 | 121 | @if($errors->has('region')) 122 | 123 | {{ $errors->first('region') 124 | }} 125 | 126 | @endif 127 |
128 |
129 | 130 |
132 | 136 |
137 | 154 | @if($errors->has('country_code')) 155 | 156 | {{ $errors->first('country_code') 157 | }} 158 | 159 | @endif 160 |
161 |
162 | 163 |
164 |
165 | 169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 | @endsection 178 | 179 | --------------------------------------------------------------------------------