Quick Start Guide
| Version | Date | Notes | By |
|---|---|---|---|
| 0.2 | 2018-01-31 | OUTDATED | JFM |
| 0.1 | 2016-10-26 | Initial release | JFM |
OUTDATED!!
This document will quickly guide you through the creation of a simple contact management
Note: This guide assumes that you have the base framework ready and working. If not, you should first follow the Installation Guide.
SGIV10 works differently then our previous SGI version. Instead of having just a backend application where everything is processed (database calls, business logic, client views, etc..) on the server, we have separated the client side from the server side.
The backend is responsible for the data access layer and the frontend is responsible for the presentation layer.
This can be a little daunting at first, but after a while all will be smother.
The separation of concerns brings a lot of advantages, since we can create a totally diferent frontend without the need to change the backend, have developers working on the frontend while others work on the backend, have a RESTfull API out of the box, etc...
So everytime we need to add some kind of functionality, most of the time we need to work on boths ends.
In this guide we will be creating a simple contact managment feature.
Migrations are a way to keep track of you database, since by default it will only apply the changes that where not yet applied, it means we no longer have to do it manually. Also, since migrations are database agnostic we usually don't need to worry about what kind of database server we are working with, as long as is supported by Laravel data abstraction layer.
Lets create a migration so we can create the table in our database.
On the console run the following command: php artisan make:migration create_t00_contacts_table
This will create a migration file on the database/migrations folder and the file name will be prefixed by a timestamp.
Something like this: Ex: 2016_06_20_105000_create_t00_contacts_table.php
Note: the table name sould always be in plural
Next, lets open the migration file and define our table structure.
The migration file, has 2 methods: up and down. The up method is where we are going to add the fields that will represent our table.
public function up()
{
Schema::create('t00_contacts', function (Blueprint $table) {
$table->increments('id');
$table->integer('number');
$table->string('name');
$table->string('email', 50);
$table->timestamps();
$table->unsignedInteger('status_id')->index();
// foreign keys
$table->foreign('status_id')->references('id')->on('t00_boolean_statuses');
});
}
The down methods is used to revert the changes of the up method. In this case we will drop the table
public function down()
{
Schema::drop('t00_contacts');
}
Now lets run the migration. This will run the up method and create the table in our database
php artisan migrate
Lets Break it down
Schema::create('t00_contacts', function (Blueprint $table) {
});
| Part | Description |
|---|---|
| Schema | Class that provides database agnostic support for creating and manipulating tables across all of Laravel's supported database systems. |
| create | this is the method we use the creata a new table |
| 't00_contacts' | The name of the table we want to create |
| function (Blueprint $table) | This is a closure (anonymous function) that receives a Blueprint object that may be used to define the new table |
$table->increments('id');
$table->integer('number');
$table->string('name');
$table->string('email', 50);
$table->timestamps();
$table->unsignedInteger('status_id')->index();
This is almost self explanatory. We use the $table object (Blueprint) to define our table structure.
| Part | Description |
|---|---|
| $table->increments('id'); | Used to create a autoincrement field named id |
| $table->integer('number'); | Used to create an int field name number |
| $table->string('name'); | Used to create a varchar field named name |
| $table->string('email'); | Used to create a varchar field named email, but this time we tell that the size should be only 50 |
| $table->unsignedInteger('status_id')->index(); | Used to create a unsigned int names status_id and add an index to it |
There are a lot of more options, you should check the Laravel manual online right Here
$table->foreign('status_id')->references('id')->on('t00_boolean_statuses');
This is a relation, we are telling laravel that the field status_id is Foreign Key and is connected to another table
| Part | Description |
|---|---|
| $table->foreign('status_id') | here we are identifying the FK wich is status_id |
| ->references('id') | here we tell the name of the field that ao FK references |
| ->on('t00_boolean_statuses') | the table of the referenced key |
Basically we are telling that the status_id field is connected to the id field on the t00_boolean_statuses table
Schema::drop('t00_contacts');
This is fairly easy. When we rollback the changes it will drop the table t00_contacts
Seeds are a way to populate your tables with test or even default production data. Altough this is not a requirement, its highly advisible to create seeds with test data on every table so it's easier to test.
Lets create a seed file
php artisan make:seeder ContactsTableSeeder
The seed file will be create in the database/seeds folder with the name ContactsTableSeeder.php
Now lets open the file and add some data.
The seed file by default contains only one method, the run method wich is executed with an artisan command
public function run()
{
//dummy data
$data = [
[ // row #0
'number' => 001,
'name' => 'Jon Smith',
'email' => 'jon.smith@wemake.pt'
],
[ // row #1
'number' => 007,
'name' => 'James Bond',
'email' => 'james.bond@wemake.pt'
],
];
//insert data in table contacts
DB::table('t00_contacts')->insert($data);
}
Last, just run the seed
php artisan db:seed or php artisan db:seed --class=ContactsTableSeeder
Lets Break it down
$data = [
[ // row #0
'number' => 001,
'name' => 'Jon Smith',
'email' => 'jon.smith@wemake.pt'
],
[ // row #1
'number' => 007,
'name' => 'James Bond',
'email' => 'james.bond@wemake.pt'
],
];
This is just an multidimenstional array where we fill with the test data we want to insert.
DB::table('t00_contacts')->insert($data);
| Parts | Description |
|---|---|
| DB | Class that runs querys |
| table('t00_contacts') | Table method with a parameter identifying the table we want to use t00_contacts |
| ->insert($data) | Method to insert the passed $data array |
Each Model corresponds to our tables in the database. This is used to work with Laravel's ORM Eloquent.
Lets create our model. Create the file app/Models/Administration/Contacts.php with the following content
<?php
namespace WeMake\Models\Administration;
use Illuminate\Database\Eloquent\Model;
use WeMake\Models\RegistersEvents;
use WeMake\Repositories\Administration\ModulesRepository;
use WeMake\Repositories\ModelRepository;
/**
* Class Module
*
* @package WeMake\Models\Administration
*/
class Module extends Model
{
use RegistersEvents;
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 't00_contacts';
/**
* The attributes that aren't mass assignable.
*
* @var array
*/
protected $guarded = ['id'];
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = ['name', 'number', 'email', 'status_id'];
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = true;
/**
* Returns an instance of proper model repository
*
* @return ModelRepository
*/
protected function repository()
{
return new ModulesRepository;
}
/**
* Returns status
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function status()
{
return $this->belongsTo(BooleanStatus::class, 'status_id', 'id');
}
}
Lets Break it down Properties
| Property | Description |
|---|---|
| $table | This property identifies our table name in the database |
| $guarded | This property is used to tell witch fields should not be able to be massively-assigned |
| $fillable | This is the reverse of $guarded, it allow us to define wich fields can be massively-assigned |
| $timestams | This is used to indicate if the model should be timestamped |
protected function repository()
{
return new ModulesRepository;
}
The method return an instance of the model repository. We will talk about repositories next.
public function status()
{
return $this->belongsTo(BooleanStatus::class, 'status_id', 'id');
}
TODO: Explain better
This method returns the status
The repository is used to mediate between the domain and data mapping layer.
The idea is to separate the data access logic with the business logic.
So lets create our table repository. Create the file app/Repositories/Administration/ContactsRepository.php with te following content:
<?php
namespace WeMake\Repositories\Administration;
use Cache;
use Illuminate\Database\Eloquent\Builder;
use WeMake\Models\Administration\BooleanStatus;
use WeMake\Models\Administration\Module;
use WeMake\Repositories\BaseModelRepository;
/**
* Class ContactsRepository
*
* @package WeMake\Repositories
*/
class ModulesRepository extends BaseModelRepository
{
/**
* Allowed record names
*
* @var array
*/
protected $allowed = [
'active_concats',
'all_concats',
];
/**
* Returns base query
*
* @return Builder
*/
protected function baseQuery()
{
return Contacts::orderBy('t00_contacts.name', 'asc');
}
/**
* Return active records
*
* @return mixed
*/
public function active()
{
return Cache::rememberForever($this->parseKey('active_contacts'), function () {
return $this->baseQuery()
->where('t00_contacts.status_id', '=', BooleanStatus::ACTIVE)
->get();
});
}
/**
* Return all records
*
* @return mixed
*/
public function all()
{
return Cache::rememberForever($this->parseKey('all_contacts'), function () {
return $this->baseQuery()->get();
});
}
}
Route are the way to tell the software where to go when it receives a Request. So everytime we whant the software to return something we nee to create a route.
Lets create the routes needed to ours requestes The routes file is located in the app/Http/Routes/Api/ folder. Each module has its own file. Open the administration.php file and add the following routes:
Route::post('concats', ['as' => 'api.administration.concats.store', 'uses' => 'ContactsController@store']);
Route::put('concats/{concat}', ['as' => 'api.administration.concats.update', 'uses' => 'ContactsController@update']);
Route::get('concats/{concat}', ['as' => 'api.administration.concats.show', 'uses' => 'ContactsController@show']);
Route::delete('concats/{concat}/destroy', ['as' => 'api.administration.concats.destroy', 'uses' => 'ContactsController@destroy']);
Route::post('concats/search', ['as' => 'api.administration.concats.search', 'uses' => 'ContactsController@search']);
Route::get('concats/all', ['as' => 'api.administration.concats.all', 'uses' => 'ContactsController@all']);
Route::get('concats/active', ['as' => 'api.administration.concats.active', 'uses' => 'ContactsController@active']);
Breakdown time!
Check this awesome route!!!
Route::post('concats', ['as' => 'api.administration.concats.store', 'uses' => 'ContactsController@store']);
What does it mean?!?!
| Part | Description |
|---|---|
| Route | Route is the classe the handles the routes |
| post | This is our verb. This route will only works when the requests are made by post |
| 'contacts' | this is the parte URL that it must mach. Something like http://localhost/contacts |
| 'api.administration.concats.store' | This is the name of the route |
| 'uses' => 'ContactsController@store' | This tells que route que will execute when we match the request. ContactsController is our controller class, store is the method that will be executed. |
Now that we have our routes, we need to create the controller that will receive the request and execute whatever we tell it to do.
Create the file app/Http/Controllers/Administration/ContactsController.php with the following content:
<?php
namespace WeMake\Http\Controllers\Administration;
use Response;
use WeMake\Http\Controllers\RestController;
use WeMake\Http\Requests\Administration\Modules\DestroyRequest;
use WeMake\Http\Requests\Administration\Modules\GetActiveRequest;
use WeMake\Http\Requests\Administration\Modules\GetActiveWithPermissionsRequest;
use WeMake\Http\Requests\Administration\Modules\GetAllRequest;
use WeMake\Http\Requests\Administration\Modules\GetAllWithPermissionsRequest;
use WeMake\Http\Requests\Administration\Modules\SearchRequest;
use WeMake\Http\Requests\Administration\Modules\ShowRequest;
use WeMake\Http\Requests\Administration\Modules\StoreRequest;
use WeMake\Http\Requests\Administration\Modules\UpdateRequest;
use WeMake\Models\Administration\Module;
use WeMake\Repositories\Administration\ModulesRepository as Repository;
use WeMake\Services\Administration\ModulesSearchService as SearchService;
/**
* Class ModulesController
*
* @package WeMake\Http\Controllers\Administration
*/
class ModulesController extends RestController
{
/**
* Returns all users
*
* @param GetAllRequest $request
* @param Repository $repository
*
* @return \Illuminate\Http\JsonResponse
*/
public function all(GetAllRequest $request, Repository $repository)
{
$modules = $repository->all();
return Response::json($modules);
}
/**
* Returns active users
*
* @param GetActiveRequest $request
* @param Repository $repository
*
* @return \Illuminate\Http\JsonResponse
*/
public function active(GetActiveRequest $request, Repository $repository)
{
$modules = $repository->active();
return Response::json($modules);
}
/**
* Display a filtered listing of the resource.
*
* @param SearchRequest $request
* @param SearchService $searchService
*
* @return \Illuminate\Http\JsonResponse
*/
public function search(SearchRequest $request, SearchService $searchService)
{
return $searchService->search($request);
}
/**
* Store a newly created resource in storage.
*
* @param StoreRequest $request
* @param Module $module
*
* @return \Illuminate\Http\JsonResponse
*/
public function store(StoreRequest $request, Module $module)
{
$operation = function () use ($request, $module) {
$module->fill($request->clean())->save();
};
$operationResult = $this->transactionService->run($operation);
return $this->responseHandler->defaultStoreResponse($operationResult->status());
}
/**
* Display the specified resource.
*
* @param ShowRequest $request
* @param Module $module
*
* @return \Illuminate\Http\JsonResponse
*/
public function show(ShowRequest $request, Module $module)
{
return Response::json($module);
}
/**
* Update the specified resource in storage.
*
* @param UpdateRequest $request
* @param Module $module
*
* @return \Illuminate\Http\JsonResponse
*/
public function update(UpdateRequest $request, Module $module)
{
$operation = function () use ($request, $module) {
$module->fill($request->clean())->save();
};
$operationResult = $this->transactionService->run($operation);
return $this->responseHandler->defaultUpdateResponse($operationResult->status());
}
/**
* Remove the specified resource from storage.
*
* @param DestroyRequest $request
* @param Module $module
*
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(DestroyRequest $request, Module $module)
{
$operation = function () use ($request, $module) {
$module->delete();
};
$operationResult = $this->transactionService->run($operation);
return $this->responseHandler->defaultDestroyResponse($operationResult->status());
}
}
Breakdown time! Ok, theres a lot of stuff in here that we didn't create
Lets add some routes to our frontend
Open the file src/common/configs/router-config.js and add the following routes:
First, the route to present the listing
{
route: 'administration/contacts',
name
:
'administration.contacts.index',
moduleId
:
'./modules/administration/contacts/index',
title
:
'Contacts List',
nav
:
true,
}
Then the route for the contact creation
{
route: 'administration/contacts/create',
name
:
'administration.contacts.create',
moduleId
:
'./modules/administration/contacts/create/index',
title
:
'Create Contact',
nav
:
true,
}
Finally the route for contact editing
{
route: 'administration/contacts/:id/edit',
name
:
'administration.contacts.edit',
moduleId
:
'./modules/administration/contacts/edit/index',
title
:
'Edir Contact',
nav
:
true,
}
Lets just breakdown the routes
| param | description |
|---|---|
| route | This is going to me our URL, something like http://localhost/administration/management/contacts |
| name | This is going to be the routes name, it cam be used later for navigation |
| moduleId | This is the path in our source code to the folder and file thate going to load when the route is matched |
| title | This is just the title of our route. |
| nav | this just tells if this route is going to be included in the router's navigation model. |
The View and the ViewModel should me placed on the respective folder according to our defined route.
<template>
<panel-group header-title.bind="headerTitle & t" header-class="bg-teal-300">
<datatable schema.bind="datatable" listing-id.bind="listingId"></datatable>
</panel-group>
<new-record route.bind="newRecordRoute"></new-record>
</template>
For the ViewModel create a file name index.js on the same folder with the following content:
import { bindable, inject } from "aurelia-framework";
import { AppContainer } from "wemake-services/app-container";
import { BaseViewModel } from "base-view-model";
import { ModulesRepository } from "./services/repository";
@inject(AppContainer, ModulesRepository)
export class ListModules extends BaseViewModel {
listingId = 'administration-contacts-listing';
@bindable headerTitle = 'form.title.listing';
@bindable newRecordRoute = 'administration.contacts.create';
@bindable repository;
@bindable datatable;
/**
* Constructor
*
* @param appContainer
* @param repository
*/
constructor(appContainer, repository) {
super(appContainer);
this.repository = repository;
}
/**
* Handles activate event
*/
activate() {
super.activate();
this.defineDatatable();
}
/**
* Handles attached event
*/
attached() {
super.attached();
}
/**
* Defines table columns
*/
defineDatatable() {
this.datatable = {
repository: this.repository,
edit: 'administration.contacts.edit',
actions: [],
buttons: [],
selectable: true,
sorting: {
column: 0,
direction: 'asc'
},
columns: [
{
data: 'id',
name: 'contacts.id',
title: '#'
},
{
data: 'number',
name: 'contacts.number',
title: 'form.field.contacts'
},
{
data: 'name',
name: 'contacts.name',
title: 'form.field.name'
},
{
data: 'email',
name: 'contacts.email',
title: 'form.field.email',
}
]
}
}
}