9 Hot Tips to Enhance Your Spark Experience
A while ago, I wrote about a product I wanted to build, to allow easy remote backups for Pagekit sites. I’ve been working on it (periodically) since then, and have come across a few interesting bits of advice.
I decided to use Laravel Spark as the foundation for the product, and I thought it would be helpful to share the advice. Whether you’re just starting your Spark app, or are in maintenance mode, I think you’ll find some of these tips useful!
1. You Don’t Have to Keep All the Base Files
You may be worried about removing too many of the base files from the standard Spark installation. When I first started, I thought it vital not to change the auth controllers (in app/Http/Controllers/Auth
), for fear that it’d break the registration and login system.
Turns out, these files aren’t used by Spark. In fact, if you add routes to them, and you try to register/log in, you’ll probably just encounter problems. These default auth controllers share the same auth guard (session driver), so logging in through one will make you authenticated through the other.
If, however, you try to register through the non-Spark controllers, your user and team accounts will be missing vital Spark information. It’s cleaner and simpler to just delete these auxiliary auth controllers.
If you’re unsure, make a full backup. Then you can roll back in case your tests pick up any regressions.
2. Use Simple Repositories
Spark includes a few simple repositories. These are like static config lists (for countries and other mostly-static data), but they can be loaded through the IoC container. They look like this:
namespace Laravel\Spark\Repositories\Geography;
use Laravel\Spark\Contracts\Repositories\↩
Geography\CountryRepository as Contract;
class CountryRepository implements Contract
{
/**
* {@inheritdoc}
*/
public function all()
{
return [
'AF' => 'Afghanistan',
'AX' => 'Åland Islands',
'AL' => 'Albania',
// ...snip
'YE' => 'Yemen',
'ZM' => 'Zambia',
'ZW' => 'Zimbabwe',
];
}
}
This is from vendor/bin/laravel/spark/src/Repositories/Geography/CountryRepository.php
We can see instances of this being used in some of the registration views:
<select class="form-control" v-model="registerForm.country"↩
lazy>
@foreach (app(Laravel\Spark\Repositories\↩
Geography\CountryRepository::class)->all()↩
as $key => $country)
<option value="{{ $key }}">{{ $country }}</option>
@endforeach
</select>
This is from resources/views/vendor/spark/auth/register-address.blade.php
I highly recommend you use these repositories for country and state data. I also recommend you use this repository style for your own lists:
namespace App\Repositories;
use DateTimeZone;
class TimezoneRepository
{
/**
* @return array
*/
public function get()
{
$identifiers = DateTimeZone::listIdentifiers(DateTimeZone::ALL);
return array_combine(
$identifiers,
array_map(function ($identifier) {
return str_replace("_", " ", $identifier);
}, $identifiers)
);
}
}
You don’t have to make an interface for each repository. In fact, I think that’s a bit of an overkill. But I think these tiny repositories are much cleaner and easier to use than the alternatives.
In addition, you can alias these in an application service provider:
namespace App\Providers;
use App\Repositories\CountryRepository;
use App\Repositories\TimezoneRepository;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* @inheritdoc
*/
public function register()
{
$this->app->singleton("timezones", function () {
return new TimezoneRepository();
});
}
}
This is from app/Providers/AppServiceProvider.php
This makes them super easy to use in views:
<select class="form-control" v-model="registerForm.timezone"↩
lazy>
@foreach (app("timezones")->get() as $key => $timezone)
<option value="{{ $key }}">{{ $timezone }}</option>
@endforeach
</select>
In general, you should try to use existing lists of data. When you absolutely have to maintain your own, I’ve found this strategy works well.
3. Don’t use caret (^) Laravel dependencies
Laravel evolves rapidly. That’s great for keeping developers interested, but it has maintenance drawbacks. Do yourself a favor and replace any ^x.x
dependency version numbers with ~x.x.x
version numbers.
Spark 1.x
is built with Laravel 5.2.x
. If you’re not careful (read: using ^5.2
), a call to composer update
will upgrade Laravel to 5.3.x
, which introduces many breaking changes.
Laravel does not claim to follow Semver, so there’s no reason to assume minor version bumps will be safe. That said, Taylor is quite reasonable when it comes to the difficulty involved in upgrading minor versions. If the breaking changes introduced take too long to mitigate, they’ll be held back until the next major version.
Spark 2.x
is based on Laravel 5.3.x
, so start there if you can. Then you’ll have access to all the shiny new features in 5.3.x
from the get-go. It’s much easier to start a new Spark application than it is to upgrade an existing one. Trust me on that…
Once again, you’re better off making a full backup before running composer update
. You’re building a product. You’ve paid money to use Spark. Backups are now a thing.
4. The Docs Are Your Friend
There’s a lot of assumed knowledge bundled into Spark. Most of the bundled views use (or at least reference) VueJS components. Spark depends on Cashier and either Stripe or Braintree.
You don’t need to be an expert on these things, but if you need to touch any of them, don’t try and do it without first going through the related documentation. Or all the documentation.
I found sections like “Adding registration fields” particularly useful, because they cover the full range of edits required to make a change.
Take a look at the free Laracasts videos as well. They’re a bit dated now, but still a good source of knowledge.
5. Don’t Feel Pressured to Learn VueJS
I know I just mentioned VueJS, but the truth is that you don’t really need to learn it before trying Spark. Before I spent a lot of time in Spark, I thought that I did. Turns out the docs tell you all you need to know, and the bundled views fill any gaps with examples.
If your goal is to do some swish-bang front-end stuff, it’s not a bad idea to learn VueJS. If all you need (JabbaScript-wise) is to add a field here or there, just follow the examples in code.
There’s nothing stopping you from making all your app-specific code plain ol’ HTML and PHP.
6. Host on Forge
Sincerely (and without any encouragement to mention them), I cannot recommend Forge highly enough. It’s sensible, affordable server management, geared towards Laravel applications.
I’ve hosted other (non-Spark) things there, but it has made my Spark experience richer. I’ve particularly enjoyed the .env
, scheduler and queued job daemon management tools. Every subscription-based app needs scheduled jobs. Every moderately complex application benefits from queued jobs.
7. Re-Arrange Middleware
One of the best features (in my personal opinion) of Laravel is route model binding. That’s when you define routes like this:
use App\Http\Controllers\CustomersController;
Route::get("customers/:customer", [
"uses" => CustomersController::class,
"as" => "customers.edit",
]);
…and then define that action as:
/**
* @param Customer $customer
*
* @return Response
*/
public function edit(Customer $customer)
{
return view("customers.edit", [
"customer" => $customer,
"groups" => Groups::all(),
]);
}
If you run this code (as a controller action), $customer
will automatically be pulled from the database. It’s a powerful feature! When I started this Spark 1.x
application, I was careful to apply global scopes to all team-specific models:
namespace App\Scopes;
use App\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class TeamScope implements Scope
{
/**
* @param Builder $builder
* @param Model $model
*
* @return Builder
*/
public function apply(Builder $builder, Model $model)
{
/** @var User $user */
$user = auth()->user();
return $builder->where(
"team_id", "=", $user->currentTeam()->id
);
}
}
This is from app/Scopes/TeamScope.php
I applied this in the models’ boot()
methods:
namespace App;
use App\Scopes\TeamScope;
use Illuminate\Database\Eloquent\Model;
class Customer extends Model
{
/**
* @inheritdoc
*/
protected static function boot()
{
parent::boot();
static::addGlobalScope(new TeamScope());
}
}
The expected behavior would be that all customers are then filtered according to logged-in users’ current teams. The trouble is that this doesn’t work in 5.2.x
, as the session isn’t started when route model bindings happen. To get around this, I had to move the session middleware out of the web
group and into the global group:
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array
*/
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Session\Middleware\StartSession::class
];
/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
// \Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
],
'api' => [
'throttle:60,1',
],
];
This is from app/Http/Kernel.php
Notice how I’ve commented out \Illuminate\Session\Middleware\StartSession::class
in $middlewareGroups["web"]
, and added it to $middleware
? Doing that starts the session before Model::boot()
, which means auth()->user()
(or Auth::user()
) will then return the logged-in user.
8. Make Your Own Helpers
I cannot tell you how many times I used the following code:
/** @var User $user */
$user = auth()->user();
/** @var Team $team */
$team = user()->currentTeam();
…before I created these helpers:
use App\User;
use App\Team;
if (!function_exists("user")) {
/**
* @return App\User
*/
function user() {
return auth()->user();
}
}
if (!function_exists("team")) {
/**
* @return App\Team
*/
function team() {
return user()->team();
}
}
They’re not crazy or complex. They’re not even a lot of code to type. But I recommend you include them from the start, because you’ll be typing (and type-hinting) the alternatives a lot.
9. Use Ngrok for Testing Web-Hooks
It’s rather common to build a product around one or more third-party services. Think about it — you’re already depending on Stripe and/or Braintree. What about when you want to post to Twitter, or send an SMS?
When that happens, you’ll often need to provide a web-hook URL. These are routes in your application that respond to (or gather data from) requests from the third-party service. It’s usually a delivery report from a transaction mail service, or confirmation data from that shipping provider.
You may be tempted to upload your application to a public server, so these third-party services can reach the web-hook URLs. You could do that (though it’s going to slow your development cycles dramatically) or you could use something like Ngrok to give your local development server an obscure public URL.
It’s easier than you think. Install Ngrok with Homebrew:
brew install ngrok
…Then serve your Laravel application (presumably with the PHP development server):
php artisan serve
…And in another tab, run Ngrok:
ngrok http 8000
You’ll see a public url (like http://7d54aeb8.ngrok.io -> localhost:8000
). When you try that URL in your browser, you’ll be able to view your application!
Conclusion
If you’re on the fence about trying Spark, I can recommend it. It’s given my product a head-start it wouldn’t have had otherwise. Hopefully these tips will save you even more time. Have you discovered any tips for Spark development? Let your fellow developers know in the comments!