Microsoft Teams / Graph API: All about Scopes

So we have our Microsoft Teams app set up, and can successfully authenticate to get a user’s information. But we’ll probably want to more with Teams. To do that, we’ll need to ask for more permissions from the user, which we’ll do during auth with “scopes”. Depending on what we want to do, we’ll need to ask specific scopes that covers that functionality. One note, we’re mostly focusing on Work accounts… Personal accounts are fairly limited in what they allow for Personal/Free Teams tenants.

Delegated vs Application scopes

The Microsoft docs have a full list of scopes that you’ll probably want to bookmark if you do anything with the Graph API. The first thing you may notice is that there are “delegated permissions” and “application permissions”.

There’s a page that goes into detail about everything, but basically, Application permissions are set on your app’s page in the Azure portal. They’ll be that same for everyone, you set the “scope” parameter to https://graph.microsoft.com/.default , and they’ll all require a Teams admin to consent for users to use them. I’ll go into admin consent in a moment.

Delegated permission are added to “scope” parameter and can either require admin consent or not. The delegated permission allow for more flexibility, so that’s what we’ll be using. Also, there are a few Graph API endpoints that don’t require an admin to consent to them, so it opens up more possibilities.

To Admin Consent or Not to Admin Consent

There are quite a few API endpoints that only provide basic information about various parts of Teams, and don’t require a Teams admin to consent to them. Though, there are still cases where an admin locks down their tenant so much that even non-admin consent endpoints may not work.

For example, if we want to get a list of Teams that a user is a member of, during auth, we’ll need to request one of these scopes: Team.ReadBasic.All, TeamSettings.Read.All, TeamSettings.ReadWrite.All, User.Read.All, User.ReadWrite.All, Directory.Read.All, Directory.ReadWrite.All. In the documentation, they list the scopes needed in order of least privileged to most privileged, meaning how much data your app will have access to. It’s best practice to use the lowest privilege that your app needs, but once you know what endpoints your app needs to hit, you may be able to use an elevated one to cover multiple endpoints.

In the “list of Teams” example, we can ask for the “Team.ReadBasic.All” scope, which does not require an admin to approve its usage. However, if we notice we also need to be able to update a user’s info, we’ll need to request “User.ReadWrite.All” which does require an admin to consent… and we could just use that single scope to cover both endpoints.

If we decide to use the “application permissions” instead of delegated ones, they ALL require an admin to consent. This likely means a non-admin user would try to login, then get stopped on Microsoft’s scopes page without being able to continue. So let’s stick with non-admin, delegated permissions to see what we can do…

Getting a channel list

For this task, we want to get all the channels a user is a member of. Luckily, we can get that data without (generally) needing an admin to approve it. Let’s update our login method:

Route::get('login', function () {
    $authUrl = 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize';
    $query   = http_build_query([
        'client_id'     => config('services.microsoft.client_id'),
        'client_secret' => config('services.microsoft.client_secret'),
        'response_type' => 'code',
        'redirect_uri'  => secure_url('/login-redirect'),
        'scope'         => 'User.Read Team.ReadBasic.All Channel.ReadBasic.All offline_access'
    ]);

    return redirect()->away($authUrl . '?' . $query);
})->name('login');

We added two new scopes, and those permission should now show up on the Microsoft login page

Now, after they get redirected, we can make the endpoint calls we want. First, to get the list of the Teams the user is in, grab the first Team from the list, then get the channels in that Team.

Route::get('login-redirect', function (\Illuminate\Http\Request $request) {
    $authReponse = \Illuminate\Support\Facades\Http::asForm()->post('https://login.microsoftonline.com/common/oauth2/v2.0/token', [
        'client_id'     => config('services.microsoft.client_id'),
        'client_secret' => config('services.microsoft.client_secret'),
        'code'          => $request->input('code'),
        'grant_type'    => 'authorization_code',
        'redirect_uri'  => secure_url('/login-redirect')
    ]);

    $accessToken = $authReponse['access_token'];

    $myFirstTeam     = \Illuminate\Support\Facades\Http::withToken($accessToken)->get('https://graph.microsoft.com/v1.0/me/joinedTeams')['value'][0];
    return [
        'id'          => $myFirstTeam['id'],
        'name'        => $myFirstTeam['displayName'],
        'description' => $myFirstTeam['description'],
        'channels'    => \Illuminate\Support\Facades\Http::withToken($accessToken)->get('https://graph.microsoft.com/v1.0/teams/' . $myFirstTeam['id'] . '/channels')['value']
    ];
})->name('login.redirect');

Now, after logging in, authenticating, and being redirected back to our site… we should get something like this:

{
  "id": "aaaaaaaa-bbbb-cccc-1111-123456789012",
  "name": "Matula Teams",
  "description": "Main Team for the Matula Tenant",
  "channels": [
    {
      "id": "19:aaaaaaaaaaaa@thread.skype",
      "displayName": "General",
      "description": "Check here for organization announcements and important info.",
      "email": "",
      "webUrl": "https:\/\/teams.microsoft.com\/l\/channel\/19%3...",
      "membershipType": "standard"
    },
    {
      "id": "19:bbbbbb@thread.skype",
      "displayName": "Teams Teamy Teamteam",
      "description": "So many Teams",
      "email": "",
      "webUrl": "https:\/\/teams.microsoft.com\/l\/channel\/19%...",
      "membershipType": "standard"
    },
    {
      "id": "19:ccccccc@thread.skype",
      "displayName": "Secret Teams. Shhhh",
      "description": null,
      "email": null,
      "webUrl": null,
      "membershipType": "private"
    }
  ]
}

Now, with just adding a few scopes, we’re able to get some good information about the user’s Teams and the channels they’re in. In the next post, we’ll see about a practical application for all this.

Microsoft Teams / Graph API: oAuth and PHP

In the previous post, we created a Microsoft Teams app and bot so we can use the Graph API. We should now have a “client id” and a “client secret” that is needed to make authentication requests, and get a user’s access token.

Install Laravel

To make things simple, we’ll be building the authentication code using the Laravel v8 PHP framework. Also please note, I’m more focused about functionality, so I won’t worry about code design and architecture. All the code will live in the routes/web.php file as part of a closure. In a production environment, we’d want to split that code out to Controllers and make sure our components are placed in logical places where we can reuse code.

There are plenty of good, existing oAuth clients for PHP, like the one from the PHP League, but we’ll be rolling our own. If you need to integrate with multiple services, I would highly recommend using one of those packages. Since all we’re doing is Microsoft, we’ll be using the HTTP client that comes with Laravel, and creating the system directly.

Auth button

Let’s start with a button that the user will click and then be directed to login with Microsoft. Open up the /resources/views/welcome.blade.php file and replace the <body> with a simple button.

<body>
    <div style="width:300px;margin:20px auto;">
        <a href="/login">
            <button style="border:1px solid #999;padding:10px">Login with Microsoft</button>
        </a>
    </div>
</body>

Now, when they click the button and go to our /login route, we need to redirect them to the Microsoft login page. So open the routes/web.php file and add the new route.

Route::get('login', function() {
   $authUrl = 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize';
   return redirect()->away($authUrl);
});

Now if we try to login, we’ll get an error page on Microsoft

We need to update the login code to add the needed parameters to our redirect url. First off, we should save our client id and secret somewhere. In the root directory of Laravel should be a .env file, where we can store our environment variables

MICROSOFT_CLIENT_ID=11111111-2222-3333-aaaa-bbbbbbbbbbbb
MICROSOFT_CLIENT_SECRET=tOtallYfAkeSecRet

Then open up /config/services.php and add a new third party configuration

'microsoft' => [
        'client_id'     => env('MICROSOFT_CLIENT_ID'),
        'client_secret' => env('MICROSOFT_CLIENT_SECRET')
  ]

Now we can update our login code to add in the needed parameters.

Route::get('login', function () {
    $authUrl = 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize';
    $query   = http_build_query([
        'client_id'     => config('services.microsoft.client_id'),
        'client_secret' => config('services.microsoft.client_secret'),
        'response_type' => 'code',
        'redirect_uri'  => secure_url('/login-redirect'),
        'scope'         => 'User.Read offline_access'
    ]);

    return redirect()->away($authUrl . '?' . $query);
})->name('login');

These are the minimum parameters we need to auth with Microsoft and get a user’s access token. There are more possible values depending on our need. The “response_type” of “code” is basically telling Microsoft we want to exchange for an access token, the “redirect_uri” is where the user goes after logging in to Microsoft, and “scope” are the permissions that the user is allowing our app to have. I have a separate post planned that will go into details about scopes, but for now, we’re just getting basic user information.

Now, if we hit our button, we’ll probably see this page:

To setup a correct redirect uri, we’ll need to venture into the AZURE PORTAL!

Updating the app in the Azure Portal

The Azure portal has a LOT in it. Seriously. Maybe that’s the curse of cloud-based admin portals, because AWS dashboard has a similar issue. But I’ll try to break it down easy enough, so we can get our app working.

Step 1, go to All Services and click “Azure Active Directory”:

Step 2, click “App registrations” in the left menu, and if you’re logged in with the same account that created the app in App Studio, you should see that app listed:

Step 3, click the app and then click the “Add a Redirect URI” link:

Step 4, click “Add a platform”. A pop-out window on the right should show up with various options. Since, we’re logging in through a website that sends the user to Microsoft to login, we’ll select “Web”. The Microsoft docs go into more detail about the other options.

Step 5, finally add the full url you want to redirect the user to after they log in. If you’re developing locally, Microsoft lets you use http://localhost/... but otherwise it must be https. Also, the uri is case sensitive, so make sure what you use in the portal is exactly the same when we send it doing the auth.

Finish Logging In

Now let’s go back and try the login button again. This time, we should be greeted on Microsoft by either a login screen, or maybe a login select list. After logging in, we’ll get a page asking us to approve the scopes/permissions we asked for.

Microsoft has two different “flavors” of user – Personal and Work. The Personal user will usually be someone with a “hotmail/live/outlook.com” email account… and the Work user will login with their company email, though on occasion it will be something like “@mycompany.onmicrosoft.com”. The two account types process things a little differently on Microsoft’s backend. For example, a Personal account permission screen will look like:

A Work account will look like:

Once you click “Accept” you’ll be taken back to the “redirect_uri” we specified in our login route. I’ve set it to secure_url('/login-redirect') which creates an “https” url for our route, though if you’re using localhost, it could be just url('/login-redirect'). We can build that route simply like so:

Route::get('login-redirect', function (\Illuminate\Http\Request $request) {
    return $request->all();
})->name('login.redirect');

Now, if you click “Accept” on the Microsoft site, you get back to our redirect page, and will show a “code” (on Work accounts, they might be a “session_state” value as well). We need to use that code and make another call to a Microsoft api to exchange it for an actual access token. Let’s update that function:

Route::get('login-redirect', function (\Illuminate\Http\Request $request) {
    $authReponse = \Illuminate\Support\Facades\Http::asForm()->post('https://login.microsoftonline.com/common/oauth2/v2.0/token', [
        'client_id'     => config('services.microsoft.client_id'),
        'client_secret' => config('services.microsoft.client_secret'),
        'code'          => $request->input('code'),
        'grant_type'    => 'authorization_code',
        'redirect_uri'  => secure_url('/login-redirect')
    ]);

    return $authReponse;
})->name('login.redirect');

There are a couple of caveats here. It doesn’t seem like the “token” api accepts a json payload, so we have to send it as application/x-www-form-urlencoded, hence the “asForm()” method. Also, for Personal accounts we don’t need the “redirect_uri” but it IS needed for Work accounts. It’s best to just leave it as a default.

Once again, if we go through the login process, we should be redirected back to our page and see the values returned when we asked for an access token:

{
    token_type: "Bearer",
    scope: "User.Read", // "User.Read profile openid email" on Work accounts
    expires_in: 3600,
    ext_expires_in: 3600,
    access_token: "eyJ123abc....",
    refresh_token: "M.R123abc..."
}

The access token for Work accounts is actually a JWT, and could be decoded at this point to get some basic info about the user. For Personal accounts, it looks like just a long random string. Also, the list of “scopes” that are returned differ depending on if it’s a Personal or Work account.

Quick note: I'll cover scopes in another post, but be warned 
that Personal account logins will strip any scopes they don't 
support, and succeed in logging in. Work accounts will return 
an error letting you know the scope wasn't accepted.

In a full app, we’ll want to store the “refresh_token”, so we can get a new access token without asking the user to log in again. We’d also store the access token, possibly in a cache since it expires in an hour.

Now we can use the token directly, and make our first request to the Graph API. With the one scope we requested, it pretty much only allows us access to the “/me” endpoint.

Route::get('login-redirect', function (\Illuminate\Http\Request $request) {
    $authReponse = \Illuminate\Support\Facades\Http::asForm()->post('https://login.microsoftonline.com/common/oauth2/v2.0/token', [
        'client_id'     => config('services.microsoft.client_id'),
        'client_secret' => config('services.microsoft.client_secret'),
        'code'          => $request->input('code'),
        'grant_type'    => 'authorization_code',
        'redirect_uri'  => secure_url('/login-redirect')
    ]);

    $accessToken = $authReponse['access_token'];

    return \Illuminate\Support\Facades\Http::withToken($accessToken)->get('https://graph.microsoft.com/v1.0/me');
})->name('login.redirect');

In this code, we grab the access token from token response. Laravel’s Http class has a handy withToken() method that adds a “Bearer” token to our request header. Then we hit the ‘/v1.0/me’ endpoint… though we could do ‘/beta/me’. Those beta endpoints are stable, and while they suggest not using them in production, it’s never been an issue and they often have more functionality.

If we login once more, we’ll get our information returned from the Graph API:

{
    @odata.context: "https://graph.microsoft.com/v1.0/$metadata#users/$entity",
    displayName: "Firstname Lastname",
    surname: "Lastname",
    givenName: "Firstname",
    id: "978.....",
    userPrincipalName: "...@hotmail.com",
    businessPhones: [ ],
    jobTitle: null,
    mail: null,
    mobilePhone: null,
    officeLocation: null,
    preferredLanguage: null
}

If you use the “/beta/me” endpoint with a Work account, you’ll actually get a LOT more information, like the licenses that are assigned to the user.

So now we:

  • Have our Teams app and bot
  • Can request an access token
  • Can get information from the Graph API

In the next post, I’ll go into more detail about the scopes and various things we can do in Teams.

Microsoft Teams / Graph API: Create a Teams app

In my role at Mio, I’ve had the opportunity to work fairly extensively with Microsoft Teams and the Microsoft Graph API. One thing I noticed is that their documentation is extensive, but can be overwhelming if you just need to do something simple. My goal is to write a few posts that simplifies the ways to integrate with Teams and use the Graph API.

Creating the app

The first step we need to take is creating an app for us to use. The easiest way to do that is through the Microsoft App Studio app.

After adding that, go into the App Studio, make sure you’re in the “Manifest editor” tab, and choose “Create a new app”. After that, you’ll be greeted with a lot of options you’ll need to make your app.

The App details are fairly self-explanatory, and you only need to worry about the ones with the star. For “App ID”, I just click the “Generate” button and it fills in a UUID for us. “Package Name” is basically like a web url, but in reverse, so mine is something like “com.terrymatula.yadayada”… “Version” should be 1.0.0 to start and should align with semantic versioning.

The various website URLs can be anything while we test. The “Branding” will need to have 2 icons, one that’s 192px by 192px, and another that’s 32×32. After that page is filled out, we should go to the Capabilities step. We can add a Tab, Bot, Connector, or Message Extension. For our purposes, we only need to worry about the Bot.

Go to the Bots step and click the Set up button. This will allows us to either connect an existing Microsoft bot we’ve already created, or create a brand new one. We’ll do a new one.

We don’t need to deal with the “Messaging bot” or “Calling bot” options at the moment. We have 3 options for “Scope”, which is basically where we want the bot to be able to work. For just one-on-one chats with the user, we choose “Personal”… for chats that include multiple users, we choose “Group Chat”… if we want to use the app in a regular Teams channel, choose “Team”. At the very least for our purposes, we’ll need to select “Team”.

After creating the bot, the screen will show the new bot with a UUID underneath. We need to save this value as our “Client ID”. Then click “Generate new password”, and it will popup a window with a password, which we need to save (it won’t be shown again) as our “Client Secret”

That’s really all we need for now. With the Client ID and Client Secret, we can then go through the oAuth process to get a Teams user’s access token and make Graph API requests, which I’ll cover in the next post.

From 0 to Complete App In 48 Hours

I’ve been unemployed for the past month, so in that time I’ve decided to work on various side projects and potential businesses. A lot of that time was spent building things with various languages (like Python and Ruby) that I’ve not worked with professionally. I also devoted quite a bit of time to learning all about React and React Native.

The first project I’ve been working on is SoundFair, a podcast app allowing listeners and podcasters an opportunity to get better connected. It’s a more serious project, so I’ve been very concerned with getting it “right”. I’m building the mobile app in React Native, and I’ve been finding the audio portion to be the biggest roadblock in completing it.

Since my layoff, there’s been a lot of time searching for a new gig, with interviews and phone calls and flights to other cities… and SoundFair has stopped getting as much attention as it should. So I thought, “I need a quick win”. Something I can start and finish quickly, with the initial idea of being a 24 hour project. But I needed an idea.

Over the summer, we’ve been doing a “point reward” system with my son, so that for various preset tasks, he gets a certain amount of points. Those points can then be exchanged for either an allowance or something he wants to buy. To this point, it’s all been tracked on printed paper. So I decided I was going to build an app that would replace the paper.

Step 1: Specs

I knew it needed a login/auth system, and that both my wife and I would need separate accounts, but could both manage the same kids. It would also need a way to add/remove/edit kid information, add/remove/edit tasks for each kid, let you click on a task (mark it as complete and award those points), redeem/remove points for whatever reason, and view a list of all the completed tasks and redemptions.

I already knew I was going to build it in Laravel (as the API backend) and React Native, since those would be the quickest. And after writing out the specs, I saw obvious “objects” I would build around.

Step 2: API and Database Architecture

Laravel has a simple auth system built in, so that took care of a User object. Then I decided on Kid, Task, and Reward as the other objects. The User would be related to the Kid using a join table, so one Kid could have multiple parents (User). A Task would belong to one Kid and a Reward would belong to a Task. With all that in mind, I was able to quickly create all the Eloquent Models and migrations I needed.

Using the specs, it was also very apparent that I would need some fairly common CRUD endpoints, like POST /kid to add a kid, GET /kid/{kid_id} to retrieve a single kid’s info, PUT /kid/{kid_id} to update their info, and DELETE /kid/{kid_id} to remove them. It would also need similar endpoints for tasks and rewards. I also had a few not-REST-ful endpoints, like kid/{kid_id}/redeem to allow for points to be subtracted.

I decided to skip Controller creation for the project, so everything is done in route closures.

From there, I wrote out all the endpoint code I needed, using the Eloquent models directly. And added a Middleware that would make sure the authenticated user actually had permission to access the specific kid endpoints.

Honestly, the basic endpoint functionality was complete within an hour and a half. There were a few tweaks as I worked on the mobile side, but most of it was knocked out very quickly.

Step 3: React Native

I decided to use React Native Expo to create the app, testing it specifically for Android since that’s what I use. The only libraries I used were React Navigation and Axios, as those are fairly standard across the RN ecosystem.

I started by determining which “screens” I needed, which lined up fairly closely with the API objects. The one difference is adding all the task, rewards, and parent functionality under the Kid screen.

My biggest struggles with RN are state management and flexbox styling. With the limited time I imposed on myself, I didn’t build out a full Context state system, since I’m still fairly novice to all that… though, in reality, that would be the best way to handle the logged in user and their kid information. So all the state management is occurring on each screen, separately.

From there, I just went screen-by-screen, writing code until I was able to get all the data displaying correctly, and in a somewhat readable fashion.

Step 4: Running the App and API

To make sure everything worked locally, I ran the api locally using a SQlite database, serving through artisan serve, and running it through ngrok. In Postman, I created some endpoint collections and tested them all out. Then in React Native, I created a base Api component that used axios with two different “base urls”, one for development (ngrok) and one for production. As I spotted problems, I was able to fix them either in the api or RN code, and I could see the results immediately.

For production, I decided on Heroku with a PostgreSQL db. It’s super easy to setup, and their free tier is decent. Once I was satisfied that the api code was good, I deployed to Heroku and changed my RN base url to the heroku url. From there, I went through all the app screens again and made sure everything worked.

Step 5: Publishing

Trying to come up with a name for anything seems to be problematic for many people, and this was no different. After consulting with my kid, we chose ‘Points 4 Kids’ as the app name. So I created a really bad icon and splash screen, and added it to the app.

Using Expo, the build process is a straight-forward (though long) process. Once it was done, I downloaded it, installed it, and ran through everything once more. I could theoretically use this apk on the Google Play store, but I’m not sure this is something I want to release. I’ve already found a couple of error messages that were just supposed to be for debugging, and frankly, I was not at all concerned with the design aesthetics.

For now, my wife and I will use it, and if some friends are interested, I may sent it their way. This whole idea began on Thursday at 5pm, and my final build was completed on Saturday, just before 5pm… so 48 hours to get a functional, full featured api and mobile app.

Step 6: Future Enhancements

If I decide to spend more time on this project, and maybe release it, there are some definite things I’d want to update…

  • The first thing I would do is make the UI a lot better. Maybe screenshot everything and hire someone on Fiverr to make it look nicer.
  • The React Native code is kind of a jumble, so I would clean that up. First, there are some repeated bits that could be put into their own components. I’d use the Context/Provider hooks for the user and kid state management, instead of re-creating the same state on every screen. Finally, I’d abstract out all the API calls into a separate model/component.
  • A better name, and icon/graphics.
  • Clean up the API code. Specifically, moving things out of the route closures and into Controllers… and returning Resources, instead of just formatted arrays.
  • Unit/Acceptance testing everywhere.

Guiltless Code #1 – There’s no “right” way

If you’re just getting started with PHP development, or even looking to increase your skills, a great place to start is PHP: The Right Way.  However, be warned that there is not a “right” way to code in PHP (or any other language, really).  But “PHP: A very good guide to writing code that might be beneficial depending on your specific scenario” doesn’t really have that ring to it.  The fact is, everyone’s situation is unique, and there’s no one way that will be best for every situation.

For an example, let’s start with the ‘Getting Started‘ section: “Use the Current Stable Version (7.1)”.  That’s great advice, and if you have the capabilities and resources that’s what you should do.  But what if you don’t have extra funds to put towards hosting and need to use a free hosting option?  Most of the free PHP hosts I found were still on PHP 5, and only one was using 5.6.  If all you’re able to use is a server with PHP 5.3 on it, I say, “Go for it!”. That’s going to be your “right way”.

As far as developing locally, check out the “All-in-one installers“.  There are plenty of sites and Reddit posts that will tell you how you need to be using vagrant or docker or something, and by all means use them if that interests you.  Just don’t feel like using WAMP or MAMP is somehow “bad” because none of the experts are using them.  The *AMP installers are pretty amazing, and they are super easy to get set up.

Finally, let’s talk about Object Oriented Programming. Over the years, a lot of influential devs have elevated Uncle Bob Martin as the sage of all things TDD, and tangentially OOP.  While he offers good advice, I tend to find his sarcastic writing and speaking style rather dismissive and detrimental to those who are new to programming and trying to get their footing.  Feel free to learn about “single responsibility” and “encapsulation” and other programming jargon usually used about OOP… but also, feel free to write a single php file with a thousand lines of procedural code. Oh, and go ahead and mix in your html, css, and even javascript into that file. Forget the buzzwords, just create something and play around.

Speaking of Uncle Bob, in a later post, we’ll discuss why you can throw your TDD and unit tests out the window.

The point is, the “right way” is totally subjective.  People talk about “best practices” and “anti-patterns”, and apply their judgments to them… but in reality, they’re just suggestions.  Sure, they may be good suggestions, but it’s all just people’s opinions and you shouldn’t feel bad about not following them.

Laravel routes – Generating a CSV

We’re currently building out an API at Basanty, and I was looking for a way to generate a simple Excel/CSV file.  Using the `artisan routes` command, you can print out a nice table in the console, or even save it by appending `> routes.txt`. The problem is the Symfony table formatter doesn’t translate well to a document.

So I created a simple route that loops through the routes, and saves them to a csv file.  This could quite easily be abstracted to a custom artisan command. Also, after generating the file, you should probably remove it immediately.

/**
 * Generate a CSV of all the routes
 */
Route::get('r', function()
{
    header('Content-Type: application/excel');
    header('Content-Disposition: attachment; filename="routes.csv"');

    $routes = Route::getRoutes();
    $fp = fopen('php://output', 'w');
    fputcsv($fp, ['METHOD', 'URI', 'NAME', 'ACTION']);
    foreach ($routes as $route) {
        fputcsv($fp, [head($route->methods()) , $route->uri(), $route->getName(), $route->getActionName()]);
    }
    fclose($fp);
});

How I use Bower and Grunt with my Laravel projects

I just ran across an excellent article on how to use Grunt with Laravel, and I thought I’d share my process. First things first… install node, npm, bower, grunt, and all that. Google it if this is new to you.

With Laravel installed, I actually use the Laravel-Assetic package for all my concatenation and minimization. That way, cacheing is taken care of automatically. Here’s my composer,json file with all the needed filters

"require": {
		"laravel/framework": "4.0.*",
		"slushie/laravel-assetic": "dev-master",
		"leafo/lessphp": "dev-master",
		"lmammino/jsmin4assetic": "1.0.*",
		"natxet/CssMin": "dev-master"
},

Next, I use Bower for all my asset dependencies. To specify where the files are saved, I needed to add a .bowerrc file in my app’s root, and added this

{
	"directory": "assets/bower"
}

I add the files to use in my bower.json file

{
  "name": "Matula",
  "version": "0.0.0",
  "homepage": "https://terrymatula.com",
  "authors": [
    "Matula <terrymatula@gmail.com>"
  ],
  "license": "MIT",
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ],
  "dependencies": {
    "underscore": "~1.4.4",
    "Eventable": "~1.0.1",
    "jquery": "~2.0.3",
    "bootstrap": "jasny/bootstrap#~3.0.1-p7",
    "sir-trevor-js": "~0.3.0",
    "jquery-ui": "~1.10.3",
    "angular": "~1.2.2"
  },
  "resolutions": {
    "jquery": "~2.0.3"
  }
}

In the command line, run `bower install` and all those files are added to the ‘assets/bower’ directory. Then (after following the Laravel-Assetic install instructions) we need to add the files to use in the Laravel-Assetic config file. Here’s an example of my config:

<?php
/**
 * Configure laravel-assetic options in this file.
 *
 * @package slushie/laravel-assetic
 */

return array(
  'groups' => array(
    // Javascripts
    'script' => array(
      'filters' => array(
        'js_min'
      ),
      'assets' => array(
        'underscore',
        'eventable',
        'jquery',
        'jqueryui',
        'redactorjs',
        'bootstrapjs',
        'sirtrevorjs',
        'sirtrevorcustomjs',
        'customjs'
      ),
      'output' => 'js/matula.js'
    ),

    // CSS
    'style' => array(
      'filters' => array(
        'css_min'
      ),
      'assets' => array(
        'bootstrapcss',
        'sirtrevoricons',
        'sirtrevorcss',
        'redactorcss',
        'customcss'
      ),
      'output' => 'css/matula.css'
    ),

    // LESS files
    'less' => array(
      // Convert LESS to css
      'filters' => array(
        'less',
        'css_min'
      ),
      'assets' => array(
        public_path('less/style.less'),
      ),
      'output' => 'css/less.css'
    ),
  ),

  'filters' => array(
    // Filters
    'js_min'  => 'Assetic\Filter\JsMinFilter',
    'css_min' => 'Assetic\Filter\CssMinFilter',
    'less'    => 'Assetic\Filter\LessphpFilter'
  ),

  // List of Assets to Use
  'assets' => array(
    // Add name to assets
    'jquery'            => public_path('bower/jquery/jquery.js'),
    'angular'           => public_path('bower/angular/angular.js'),
    'bootstrapjs'       => public_path('bower/bootstrap/dist/js/bootstrap.js'),
    'eventable'         => public_path('bower/Eventable/eventable.js'),
    'jqueryui'          => public_path('bower/jquery-ui/ui/jquery-ui.js'),
    'jqueryfileapi'     => public_path('bower/jquery.fileapi/jquery.fileapi.js'),
    'sirtrevorjs'       => public_path('bower/sir-trevor-js/sir-trevor.js'),
    'underscore'        => public_path('bower/underscore/underscore.js'),
    'sirtrevorcustomjs' => public_path('js/custom/sir-trevor-custom-blocks.js'),
    'redactorjs'        => public_path('js/custom/redactor.js'),
    'customjs'          => public_path('js/custom/bs.js'),

    'bootstrapcss'      => public_path('bower/bootstrap/dist/css/bootstrap.css'),
    'sirtrevoricons'    => public_path('bower/sir-trevor-js/sir-trevor-icons.css'),
    'sirtrevorcss'      => public_path('bower/sir-trevor-js/sir-trevor.css'),
    'redactorcss'       => public_path('css/custom/redactor.css'),
    'customcss'         => public_path('css/custom/custom.css'),
    'customless'        => public_path('less/*')
  )
);

Some explanation: Starting at the bottom, we have our ‘assets’ array, where we assign a name to each asset and point it to the file we want to use. Then, we have our ‘filters’ array where we give a name to each filter we’re using. The composer file loaded in the filters we’re using. Before that, we create our ‘groups’ array. Each group is basically the filters we want to use, the files we want to filter, and then the output file to create. The order of the files is important, since this will affect any dependent files (eg, jquery needs to be before jquery-ui).

Now, we can run `php artisan asset:warm’ and all our files will be created. The one issue I had during development was that I was doing lots of quick updates to the css/js, and it would take time for the asset cache to clear. So I had to ‘warm’ them often. This was a time waste, so I’m using grunt to take care of that for me, ad live-reload it as well.

The only 2 grunt packages we need are ‘grunt-contrib-watch’ and ‘grunt-exec’. This is how my Gruntfile.js file looks:

module.exports = function(grunt) {

  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),

    exec: {
      warmscript: {
        cmd: 'php artisan asset:warm script'
      },
      warmstyle: {
        cmd: 'php artisan asset:warm style'
      },
      warmless: {
        cmd: 'php artisan asset:warm less'
      }
    },
    watch: {
      options: {
        livereload: true
      },
      less: {
        files: ['assets/less/*.less'],
        tasks: ['exec:warmless']
      },
      css: {
        files: ['assets/css/custom/*.css'],
        tasks: ['exec:warmstyle']
      },
      js: {
        files: ['assets/js/custom/*.js'],
        tasks: ['exec:warmscript']
      },
      html: {
        files: ['app/views/*.php', 'app/views/**/*.php']
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-exec');

  grunt.registerTask('default', ['exec', 'watch']);

};

I open a dedicated command line tab, run `grunt` and it will warm everything on start. Then, anytime I make a change to one of the files, it’s automatically warmed and the page is reloaded. The ‘html’ part will reload it when my views are changed.

One word of warning… the way I have it set up, it loads every js/css into every view. While it’s handy to only have 2 (or 3 in my case) minimized files that need to be loaded, we’ll only need Redactor on the pages with forms.  It might be best to create our groups dynamically, and only use the assets we need for each view.

My Journey writing a Laravel Book

It all started 2 years ago, when I found this post on Forrst (archived.org) by Taylor Otwell announcing the release of Laravel 2.  I downloaded and tried it.  As I was using Codeigniter at the time, I found it quite nice and refreshingly different. However, my job only had PHP 5.2 on its server, so I only tried it locally and didn’t go much further. Then a few months later, he announced version 3 and I gave it more time.  When version 3.1 was released, I ended up using it for some personal projects and read through the source code pretty extensively. At that point, I considered myself fairly ‘expert’ in Laravel.

In August of 2012, I was contacted by Packt Publishing to be a ‘technical reviewer’ for Shawn McCool‘s Laravel Starter book. It was a fairly easy thing, and payment was basically just a hard copy of the book and my name/bio in it. When it was released, I was pretty excited to see my name in print. This was my basic reaction.

Shortly thereafter, I was contacted again by Packt with the offer to write a Laravel book, specifically a ‘cookbook‘.  It seemed like a daunting task, but I figured it would be a great step in my career, so I agreed.  The money isn’t great, but since I’m fairly unknown and payment was guaranteed, I was pretty happy with the compensation.

Packt pumps out tons of niche tech books every month, so they have a solid system set in place.  The first step was an outline. I had to come up with chapters, and then ‘recipes’ for those chapters with an estimated page count.  At this point, I started to realize that page counts and recipe counts were a pretty big deal to them.  They wanted 11 – 12 chapters with 9 or 10 recipes in each.  They said it looks good to have ‘over 100 recipes’ on the cover.   I struggled a bit at first, since I had done absolutely nothing like this, ever.  Eventually, after reading forum posts and seeing IRC questions, I began to get an understanding of what people needed help with.  It made the outline process a bit easier.

With the outline complete and approved by Packt, I began working on the chapters.  It was actually a lot easier than I first thought.  The only real issue I had was how the whole thing was formatted.  You get a Word document template, and you’re supposed to format everything with their pre-made styles. Formatting is VERY important to them. Also, everything is supposed to be worded like “and then we do this” or “so our next step is” because I guess it makes the reader feel part of the book or something. Formatting was probably the most stressful part of the initial process.

Chapters 1 to 9 actually went fairly easily. At this point in January 2013, version 4 of Laravel had already been announced, and the beta release was on Laravel’s develop branch on GitHub.  Then someone who was doing a technical review asked if this was for version 3 or version 4.  Packt asked me about it, and I told them I was working on L3, since L4 was still in early beta.  But most people’s best guess was a summer release of L4, and it would be quite a bit different from L3.  Packt and I decided it would be best to wait, because the release date for the book would probably be around the time L4 was released… and thus would instantly be irrelevant.

Now, it’s the end of February and I go to Laracon in DC. It was a fun time and I got to meet some excellent people. We also find out that L4 would be released in May.  So now, all I needed to do was learn L4.  With work and family, it wasn’t very easy to dig that deep into version 4.  The source is pretty extensive and relies on a lot of 3rd party libraries.  Packt was pretty urgent about wanting the thing finished, so I decided to stick with the original outline, salvage what I could, and just update the syntax for L4.  A few chapters needed a major overhaul, like one I wrote about using and creating L3 Bundles or how to include and use Composer in an L3 app. There are some that are still in the book, and work, but are kind of silly to do when using L4… for example, installing L4 as a git submodule.  Unfortunately, there are also a couple of DGAF chapters, like 3 separate chapters on using Twitter/Facebook/Linkedin for auth/logging in.  I ended up just getting it done and turning it in, and trying to make it as good as it could be.  I fell short of the 100+ recipes by 10 or so, and the 300+ pages by about 50.  Oh well. By the end of October, the Laravel Application Development Cookbook was officially published.  I can now call myself an author.

Now that I’ve actually spent some time with L4 and competed some projects with it, I’m kind of sad that there are certain bits missing.  Things like using the Laravel workbench, or creating custom commands, or service providers. I would add, update, or replace a good 20% of the book if I were to re-do it today.

Having said that, I still think it’s worth the money you pay, especially for the hard copy. Even just the ebook is less $ than two of the more popular Laravel ebooks available at the moment, and I think the information contained is just as valid. And while some may not have a great opinion about Packt, I was overall fairly happy with them.  I think their focus on page counts and formatting makes the process a little less enjoyable, but they promised a certain payment at various points in the process, and they delivered in a timely manner.

I think Packt is great for an unknown with a decent amount of knowledge on a subject, especially if you’ve never written anything before. Just having a published book on Amazon has opened a few doors for me, and I’ve seen an uptick in blog readers and Twitter followers. Though, if you’re even remotely known for a particular subject, just go to LeanPub… where you would need to just sell 150 books at $20.

In closing… I got my name on a book, I got a nice printed dedication to my wife and kid, and I got a little money.  So I’m pretty happy with the experience.  I mean, I may never do it again… but I’m happy I did it once.

 

Laravel Application Development Cookbook

Hey look! I wrote a book about Laravel: http://bit.ly/laravelcookbook

Some of the fun and exciting things you’ll learn…

  • Setting Up and Installing Laravel
  • Using Forms and Gathering Input
  • Authenticating Your Application
  • Storing Data
  • Using Controllers and Routes for URLs and APIs
  • Displaying Your Views
  • Creating and Using Composer Packages
  • Using Ajax and jQuery
  • Using Security and Sessions Effectively
  • Testing and Debugging Your App
  • Deploying and Integrating Third Party Libraries

 

Adding foreign keys to Laravel’s migrations and schema builder

At work, there was an issue with using Laravel’s migrations and the scheme builder with tables that needed foreign keys. It’s not in the official documents, but after some source code searching, there is a solution that works well…

For example, you have a Users table and each User has Pets (one-to-many). If the User is deleted from the database, you want all their Pets to be deleted as well. In our up method in the migration, we would do something like this:

Schema::table('users', function($table) {
    $table->increments('id')->unsigned();
    $table->string('email');
    $table->string('name', 150);
    $table->timestamps();
});

Schema::table('pets', function($table) {
    $table->increments('id')->unsigned();
    $table->integer('user_id')->unsigned();
    $table->string('name', 50);
    $table->foreign('user_id')->references('id')->on('users')->onDelete('CASCADE')->onUpdate('CASCADE');
});

edit: cascade, not cascase. (ht David Stanley)