- Clone project
composer install
in project root- Clone Laradock into project (if doesn't already exist)
- Copy the
laradock/.env.example
tolaradock/.env
- Copy the
.env.example
in the project root to.env
. - Change MYSQL_HOST to
mysql
. Add AWS S3 credentials (or CDN images won't display). - Start the server:
cd laradock && docker-compose up -d nginx mysql redis elasticsearch
- Add the DB tables and seed them:
docker-compose exec workspace php artisan migrate && docker-compose exec workspace db:seed
You're good to go!
This project is setup to use Docker, but Laravel allows you to use various other methods to deploy a development environment. Regardless of what method you choose, you'll be required to setup an ENV file.
There's a sample User Seed that includes an admin. You can copy this format and run the seed.
Or you could also register for a new user, and then alter the SQL table to modify the user's type
to admin
.
- If you're on Windows, make sure PHP is installed globally (
php -v
should work in your Command Line) - Create an empty
database/database.sqlite
file. - Change the ENV file to
DB_CONNECTION=sqlite
. php artisan serve
- Follow Docker setup instructions below.
cd laradock && docker-compose up -d nginx mysql redis elasticsearch
- Clone laradock (if not already included as a submodule)
- Duplicate the
laradock/.env.example
aslaradock/.env
- Change the MySQL host in your ENV file to
mysql
(Laradock names servers for services internally instead of using localhost with ports)
If you use Laradock on other projects on the same machine, make sure to change DATA_PATH_HOST
to include your project name, and the SQL DB to be unique. Otherwise you'll share data and lose data (like the SQL DB) between projects.
One liner (from project root):
cd laradock && docker-compose up -d nginx mysql redis elasticsearch
cd laradock
from project rootdocker-compose up -d nginx mysql redis elasticsearch
docker-compose exec workspace npm install
docker-compose logs
docker-compose ps
- When first installing project and Laradock, make sure to clear project's cache (rename
/project-name/bootstrap/cache/config.php
toconfig_.php
) - Change
.env
file'sDB_HOST
tomysql
instead oflocalhost
. Laradock labels it's containers by name.
List all artisan commands:
docker-compose exec workspace artisan list
List all registered routes:
docker-compose exec workspace php artisan route:list
Make a controller:
docker-compose exec workspace php artisan make:controller ShopsController
Add new composer dependency:
docker-compose exec workspace composer require phpmailer/phpmailer:5.2.*
Reset composer namespaces:
docker-compose exec workspace composer dump-autoload
Make Controller:
php artisan make:controller ProductController
Clear config cache when changing files after deploy:
php artisan config:cache
Check for AJAX request in controller Great for GET routes that need to double as AJAX endpoints.
public function index(Request $request)
{
if($request->ajax()){
return "AJAX";
}
return "HTTP";
}
Use Carbon instead of PHP's Date\Time
class.
Format Date/Time value into proper DB format:
$date_estimated_arrival = new Carbon($request->input('date_estimated_arrival'));
echo $date_departure->toDateTimeString();
Format Date value into proper DB format:
$date_estimated_arrival = new Carbon($request->input('date_estimated_arrival'));
echo $date_departure->toDateString();
Images in forms
Always include enctype
param for forms including images.
<form method="POST" action="/business/admin/photos/" enctype="multipart/form-data" class="ui form" >
Error Handling:
@if ($errors->any())
<div class="ui negative message">
<i class="close icon"></i>
<div class="header">
There were some errors with your submission
</div>
<ul class="list">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
- Add event and listener to $listen array in
EventServiceProvider.php
php artisan event:generate
- Add event to page:
event(new PodcastPurchased($podcastId, $podcastUrl))
Queue Events:
- Open event listener class and add
class EventHandlerName implements ShouldBeQueued
- Above class (at the top of file) add
use Illuminate\Contracts\Queue\ShouldQueue;
- If you need access queue job functions (like
$this->delete
) - inside event listener class, adduse InteractsWithQueue
Add job to queue:
Make the job class first:
php artisan make:job ProcessPodcast
Then dispatch the job (make sure to require class deps):
ProcessPodcast::dispatch($podcast);
Make mail template:
Make the job class first:
php artisan make:mail OrderShipped
Add subject and send variables to email:
return $this->subject("Email_Subject_Here")
->view('emails.routes.contactform')
->with($this->variableHere);
Ideally you pass a $this
based var
Laravel uses PHPUnit for testing. All tests are located in the /tests/
folder.
Make a new test
php artisan make:test TestName --unit
Unit flag is optional to create a "unit test" vs a "feature test". Unit = one function, feature = multiple controllers/models
Testing API
Laravel offers a JSON method inside the test class ($this->json()
) that allows you to query the internal API with different request methods (GET, POST, DELETE, etc). The JSON method returns a response, which offers a assertJson
method to verify JSON matches what we expect.
Inside the test method/function:
$shop = factory(\KushyApi\Posts::class)->states('shops')->make();
$shop = $shop->toArray();
// Category is required (see StoreShops Request validator)
$shop['category'] = 1;
$response = $this->json('POST', 'api/v1/shops', $shop);
$response
->assertStatus(201)
->assertJson([
'data' => [
'name' => $shop['name'],
'categories' => [
['category_id' => $shop['category'] ]
]
]
]);
Creating fake data for testing
Laravel uses the Faker library inside factories that generate models filled with fake data (database/factories
). You can use these factories inside tests to quickly create objects to query or use.
You can chain the factory with a states()
method that accepts comma separated strings (or an array of strings). These are defined in the factories, reference there for all the options. The chains add extra parameters/fields to the created model, like adding a section (in this case 'shops') to Posts.
$shop = factory(\KushyApi\Posts::class)->states('shops')->create();
$shop = factory(\KushyApi\Posts::class)->states('shops')->make();
$shop = $shop->toArray();
Authenticating / Adding JWT to POST
Add the AttachJwtToken
to the test.
use Tests\Traits\AttachJwtToken;
Running all tests
docker-compose exec workspace ./vendor/bin/phpunit
Any tests involving SQL must be run through Docker, since the SQL DB is hoisted inside the container (and you're running from outside).
Testing Tips
- Always chain a unique() to fields like user_id when creating factories:
'username' => $faker->unique()->userName,
Check if user is authenticated (Blade) - see more:
@if(Auth::check())
If user is guest:
@if(Auth::guest())
.
Auth - Get username
{{ Auth::user()->name }}
Route with Authentication check middleware:
Route::get('/my-path', [
'middleware' => ['auth'],
'uses' => function () {
echo "You are allowed to view this page!";
}]);
Middleware applied to specific methods in controller:
Apply one of these in the constructor method of the controller.
public function __construct()
{
// Only apply middleware to these methods
$this->middleware('auth', ['only' => ['create', 'store', 'edit', 'delete']]);
// Or exclude middleware from these methods
$this->middleware('auth', ['except' => ['confirmPage', 'confirmOrder', 'invoice']]);
}
Seed Database:
If you've added any new seeders:
composer dump-autoload
Then run the seed (this will run all the seed classes listed in DatabaseSeeder.php
:
php artisan db:seed
or
php artisan migrate:refresh && php artisan db:seed && php artisan db:seed --class=StrainsTableSeeder
to rollback and rebuild entire DB with a specific seed class
Debug the seed using the verbose command
php artisan db:seed --class=YourSeeder -vvv
Create DB migration file:
php artisan make:migration create_table_name_table --create=table_name
Sample migration file:
Schema::create('links', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->string('url')->unique();
$table->text('description');
$table->timestamps();
});
Run the migration:
php artisan migrate
Update a table with new columns:
php artisan make:migration add_paid_to_users
public function up()
{
Schema::table('users', function($table) {
$table->integer('paid');
});
}
public function down()
{
Schema::table('users', function($table) {
$table->dropColumn('paid');
});
}
Dynamically call Models:
$dynamic_model = 'Shops';
$model_name = '\\Kushy\\'.$dynamic_model;
$model = new $model_name;
$shops = $model->find($id);
LIMIT and OFFSET
$users = DB::table('users')
->offset(10)
->limit(5)
->get();
Basic template:
@extends('layouts.main')
@section('title', 'Page Title')
@section('content')
Content here
@stop
Link to public assets path in template:
{{ public_path('fonts/OptimusPrinceps.tff') }}
Requires Illuminate\Http\Request as $request
Get specific input:
$this->request->input('input-name');
Get all input as array:
$this->request->all();
Add to the top of any file using Redis facade:
use Illuminate\Support\Facades\Redis;
Grab a Redis key:
$user = Redis::get('user:profile:'.$id);
Set a Redis key:
$user = Redis::set('user:profile:'.$id);
Access session data:
$value = $request->session()->get('key');
# Emergency: system is unusable
Log::emergency($message);
# Alert: action must be taken immediately
Log::alert($message);
# Critical: critical conditions
Log::critical($message);
# Error: error conditions
Log::error($message);
# Warning: warning conditions
Log::warning($message);
# Notice: normal but significant condition
Log::notice($message);
# Informational: informational messages
Log::info($message);
# Debug: debug-level messages
Log::debug($message);
Show a collection's pagination navigation using the Semantic UI template:
$shops->links('vendor.pagination.semantic-ui')
Using components in a loop with @extends? @overwrite, not @endsection:
@section('stuff')
Stuff goes here...
@overwrite
Using UUIDs for models:
Use this instead of incrementing()
in migration:
$table->uuid('id');
$table->primary('id');
Add this to your model:
/**
* Generates and inserts uuid when creating new items
*/
use Uuids;
Uuids will be automatically generated on post creation.
Within your controllers, before you perform a redirect, make a call to the flash()
function.
public function store()
{
flash('Welcome Aboard!');
return home();
}
You may also do:
flash('Message')->success()
: Set the flash theme to "success".flash('Message')->error()
: Set the flash theme to "danger".flash('Message')->warning()
: Set the flash theme to "warning".flash('Message')->overlay()
: Render the message as an overlay.flash()->overlay('Modal Message', 'Modal Title')
: Display a modal overlay with a title.flash('Message')->important()
: Add a close button to the flash message.flash('Message')->error()->important()
: Render a "danger" flash message that must be dismissed.
With this message flashed to the session, you may now display it in your view(s). Because flash messages and overlays are so common, we provide a template out of the box to get you started. You're free to use - and even modify to your needs - this template how you see fit.
@include('flash::message')
- Use namespace
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Client;
- Use it!
$client = new Client(); //GuzzleHttp\Client
$res = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle');
echo $res->getStatusCode();
// 200
echo $res->getHeaderLine('content-type');
// 'application/json; charset=utf8'
echo $res->getBody();
// '{"id": 1420053, "name": "guzzle", ...}'
$result = $client->post('your-request-uri', [
'form_params' => [
'sample-form-data' => 'value'
]
]);
// POST
Get user location data:
geoip($ip);
Example data:
\Torann\GeoIP\Location {
#attributes:array [
'ip' => '232.223.11.11',
'iso_code' => 'US',
'country' => 'United States',
'city' => 'New Haven',
'state' => 'CT',
'state_name' => 'Connecticut',
'postal_code' => '06510',
'lat' => 41.28,
'lon' => -72.88,
'timezone' => 'America/New_York',
'continent' => 'NA',
'currency' => 'USD',
'default' => false,
]
}
Display default location data:
geoip($ip = null);
Make sure to include highlight.js
and the syntax color style, and then activate the script:
<link rel="stylesheet" type="text/css" href="{{ asset('css/atom-one-light.css') }}">
<script src="{{ asset('js/highlight.pack.js') }}"></script>
<script>hljs.initHighlightingOnLoad();</script>
Add a code snippet to a page:
<pre>
<code class="php">
<?php echo('something') ?>
</code>
</pre>
- Swap
.env
file with data fromproduction.env
- Push git commits to Heroku
- Check website for changes
- Swap back
.env
file with data fromdevelopment.env
Deployment with AWS is completed through Git. You'd add the staging server's git URL to your project, and push to master.