Selecting a team via the URL in Laravel Spark
Spark is an awesome way to get started writing applications without needing to build in user authentication, team management and payment processing. The application I'm working on at the moment is team focused, so I wanted to charge a team rather than individual users. I've got a feeling that most of the applications I write are going to be centered around team billing, so here's a one stop guide to configuring Spark for teams.
Bootstrap your application
bash
composer global require laravel/installersudo npm -g install gulpspark new -n --team-billing APP_NAME
Configure your environment
Create a MySQL user and database for the application, then edit the .env
file with these credentials and run database migrations
bash
export DB_NAME=myappexport DB_USER=myappexport DB_PASS=somepasswordmysql -u root -e "GRANT ALL on $DB_NAME.* TO $DB_USER@'localhost' IDENTIFIED BY '$DB_PASS';"mysql -u root -e "CREATE DATABASE myapp;"sed -i "s/DB_DATABASE=.*/DB_DATABASE=$DB_NAME/" .envsed -i "s/DB_USERNAME=.*/DB_USERNAME=$DB_USER/" .envsed -i "s/DB_PASSWORD=.*/DB_PASSWORD=$DB_PASS/" .envphp artisan migrate
Enable accessing teams by path
By default, Spark uses the session to know which team you're looking at. I'm not a fan of this as it means a link can have different meanings depending on your session state. I prefer having all the info in the URL so that you can share links with team mates. Fortunately, Spark also supports this.
Edit ./app/Providers/SparkServiceProvider.php
and add the following to the booted
method:
php
Spark::identifyTeamsByPath();
Whilst you're in there, you probably want to add your email address to the $developers
class parameter.
At this point, you should be able to run php artisan serve
and visit http://localhost:8000/ to register an account (I've called my first team Demo
). Once you've registered and logged in, immediately click on the logo in the top right and register another team (this one's called Something
).
Getting the team from the URL
To get the team from the URL, you'll need to set up a route to capture the current team. Open up routes/web.php
and take a look at the home route. We're going to update it so that it contains the team slug:
php
Route::get('/{team_slug}/home', 'HomeController@show');
Next, we'll update app/Http/Controllers/HomeController.php
so that it outputs the name of the current team. Add the following on line 28:
php
echo \Auth::user()->currentTeam->name;exit;
Finally, visit the page in a browser. We changed the route so our normal /home
URL won't work any more. Instead we need to visit http://localhost:8000/demo/home. Hopefully you'll see the same of your current team on the screen. If we change the URL to http://localhost:8000/something/home I'd expect the team name to change, but it doesn't. We have to handle detecting the team ourselves.
Changing team automatically
Changing the team that we're on automatically is a job for middleware! At this point we're going to be writing code so we need to create a directory for it and update the Composer autoloader so that it knows where to find it.
bash
mkdir -p src/Middlewarejq '.autoload["psr-4"]["mheap\\"] = "src"' composer.json > c2.jsonmv c2.json composer.jsoncomposer dump-autoloadtouch src/Middleware/FetchCurrentTeamFromUrl.php
Edit src/Middleware/FetchCurrentTeamFromUrl.php
and add the following contents (I share the current team's slug with all views too):
php
<?phpnamespace mheap\Middleware;use App;use Closure;use Exception;use Route;use View;class FetchCurrentTeamFromUrl {public function handle($request, Closure $next){$params = Route::current()->parameters();if (!isset($params['team_slug'])) {throw new Exception('No team set, but one was expected');}$team = App\Team::where('slug', $params['team_slug'])->first();abort_unless($request->user()->onTeam($team), 404);$request->user()->switchToTeam($team);View::share('currentTeamSlug', $team->slug);return $next($request);}}
Update app/Http/Kernel.php
and add the following to $routeMiddleware
:
php
'fetchTeam' => \mheap\Middleware\FetchCurrentTeamFromUrl::class,
Finally, we need to enable this middleware on our routes. Open up routes/web.php
again and replace the line that points to the home controller with the following:
php
Route::group(['prefix' => '{team_slug}', 'middleware' => ['auth', 'fetchTeam']], function(){Route::get('/home', 'HomeController@show')->name('home');});
Note how we named the route - that will be important later. Now, if we visit http://localhost:8000/demo/home it should say "Demo", and if we visit http://localhost:8000/something/home it should say "Something". We're now dynamically switching team based on the URL!
Enabling the team switcher
We're doing well, but the team switcher that you normally have in the top right of the page no longer lists your teams - it only lets you create a new one. Let's fix that now.
Edit app/Http/Controllers/HomeController.php
again and remove the lines we added earlier so that the home view loads fine. If we click the icon in the top right it doesn't show our teams. This section of the UI is controlled by resources/views/vendor/spark/nav/teams.blade.php
. Edit that file now.
I'm sure there's a way to do this with Vue, but I don't know enough to accomplish that, so I fell back to PHP. Delete everything after the comment <!-- Switch Current Team -->
and replace it with the following:
php
@if (isset($currentUser))@foreach ($currentUser->teams as $t)@php $currentParams['currentTeam'] = $t->slug; @endphp<li><a href="{{ route($currentRoute, $currentParams) }}">@if ($currentUser->current_team_id == $t->id)<span><i class="fa fa-fw fa-btn fa-check text-success"></i>{{ $t->name }}</span>@else<span><img src="{{ $t->photo_url }}" class="spark-team-photo-xs"><i class="fa fa-btn"></i>{{ $t->name }}</span>@endif</a></li>@endforeach@endif
Finally, there are three variables that we need to make available to every view, $currentUser
, $currentRoute
and $currentParams
. The first is so that we can iterate over their list of teams and output them in the menu, and the second is so that we can make the link go to the same page but for a different team. The final variable is so that URL generation is handled automatically if there are any additional parameters available in the URL. The easiest way to ensure that these are available is to add some more middleware:
bash
touch src/Middleware/ProvideUserAndRoute.php
Edit that file and add the following contents:
php
<?phpnamespace mheap\Middleware;use Auth;use Closure;use View;class ProvideUserAndRoute {public function handle($request, Closure $next){$route = $request->route();View::share('currentUser', Auth::user());View::share('currentRoute', $route->getName());View::share('currentParams', $route->parameters());return $next($request);}}
Register this middleware in app/Http/Kernel.php
and add the following to $routeMiddleware
:
php
'provideUserRoute' => \mheap\Middleware\ProvideUserAndRoute::class,
Then update your route in routes/web.php
to add provideUserRoute
to the list of required middlewares:
php
Route::group(['prefix' => '{team_slug}', 'middleware' => ['auth', 'fetchTeam', 'provideUserRoute']], function(){Route::get('/home', 'HomeController@show')->name('home');});
At this point, you can visit your application again and take a look at the menu in the top right. You should see all of your teams listed and be able to click on them to go to the current page, but on a different team.
The end
I learned all of this through reading various docs, lots of Google searches and a little bit of my own invention. I'm new to Laravel and this probably isn't the best way to go about it but it works for me. If you've got a better solution, please do let me know!