Installing Laravel Spark Manually with Composer – 2021

For whatever reason, you may need to install the new Laravel Spark into your project without following their installation instructions. Sometimes you don’t control the server environment as much or devops, etc so it’s just easier to include the files in your project. Here’s how to tell Composer where the files are and how to load Spark.

First – Update composer.json

Similarly to the official install docs, you need to add a snippet to your composer.json file.

"repositories": [
   {
     "type": "path",
     "url": "./spark-stripe"
   }
],

In my case, I have a folder in the root of my project called spark-stripe in which I placed all of the package files. Does it matter what the name is? I don’t know but since that is the package name, it made the most sense to me.

Final – Install the Package

Lastly, you’ll need to install the package like you normally would. For what it’s worth, I’m using Composer v2.

composer require laravel/spark-stripe 

If you don’t see a new list of dependency packages being installed, you likely did something wrong. Go back and check your composer.json file for spelling errors or other mistakes.

Hope this helps you out!

How to add a Macro to the Laravel HTTP client facade

While working on Let Them Eat 🍰I came across some peculiar behavior. For some of the Slack web API’s, there is a requirement to send the requests as a URL encoded form object. Since all of their API endpoints support this method of access, I created a little helper on my user object to get an instance of the Laravel HTTP client, with the asForm() method already applied.

This was working great until today when I wanted to add support for blocks to one of my bot responses. While I could support sending that field as a JSON string, it felt better to change the request to be sent fully as JSON instead. I thought, that would be as simple as calling ->withHeaders() again, but unfortunately, the deep-recursive-merge used by the HTTP facade doesn’t clear out any existing values.

Http::asForm()->asJson()

"headers" => [
  "Content-Type" => [
    0 => "application/x-www-form-urlencoded",
    1 => "application/json"
  ]
]

Obviously, the best option would be to not call the facade with multiple methods that change the same object, but in this case I really wanted to just overwrite it for this one instance.

Enter, Macros!

There are lots of places online to read about Laravel Macros so I won’t go into it here too deeply, but the gist of it, is you can add custom methods to core objects without extending them and creating new classes. This can be super helpful when you just want to add a little helper method but don’t want to go through the process of extending the class the old-fashion way. This can be especially useful to access protected properties or private methods.

I knew all facades in Laravel are macroable so I jumped into my AppServiceProvider boot method and added a lil somthin somethin.

// within AppServiceProvider::boot()
PendingRequest::macro('clearContentType', function () {
  $this->options['headers']['Content-Type'] = [];
  return $this;
});

Originally, I tried to add the Macro directly to the Http facade, but that only ended up working if I called the new method statically. To have it work in the manner I expected, I had to add the macro to Illuminate\Http\Client\PendingRequest, which is the class that is bound to the Http facade under the hood.

Using my new macro, I can easily clear out any content type headers before making a request, no matter how many times I call methods that set the content type header.

Http::asForm()->asJson()->asForm()->asJson()->asForm()->clearContentType()->asJson()

"headers" => [
  "Content-Type" => [
    0 => "application/json"
  ]
]

Now, I suspect this is a bug, but I’m not sure. I’ll be opening an issue on Github and we shall see. In the meantime, the macro will have to do! 🙂

Migrating Data and Merging Models in Laravel

On one of my side projects, Let Them Eat 🍰 , I recently needed to do some migrations to combine / merge two models. I originally had optimized a bit too much, and later realized things would be a lot simpler if I only had one model.

There wasn’t a ton of info about this online, so I’m going to do my best to try and explain what I did. For this migration, I was using Laravel 8.

So turns out, merging models is kind of a pain! In my case, I was merging a SlackUser::class model with the default User::class model that ships with Laravel. From a data perspective, it wasn’t too bad. I needed to add a few columns to the User table that were previously on the SlackUser table. The issues arose when I realized there were a lot of places in my code that were reliant on accessing each model off of a relationship of the other. So lots of calls to $user->slackUser and $slackUser->user intermingled across the app, dependent on what I was doing at the time.

Since my ultimate goal was to completely delete any reference to a SlackUser, I had to take a careful approach when modifying the database.

First, I added the extra columns I needed to the users table.

Schema::table('users', function (Blueprint $table) {
  $table->string('slack_user_id')->after('id')->nullable();
  $table->foreignId('team_id')->after('slack_user_id')->nullable()->constrained()->onDelete('cascade');
  $table->boolean('is_owner')->after('team_id')->default(false);
  $table->string('avatar_url')->after('email')->nullable();
  $table->string('timezone')->after('avatar_url')->nullable();
  $table->boolean('is_onboarded')->after('timezone')->default(false);
  $table->softDeletes();
});

I continued in much the same process for other tables that needed to be modified.

After the tables were modified, I queried all of the SlackUsers and looped over them to create new User models if they didn’t have one already. In my app, a User model was only created if the person logged into the webapp, otherwise they would happily live on as only a SlackUser. Now, everyone gets a user model, and I don’t really care if they log in or not!

Some advice on the Shifty Coders Slack recommended not to rely on Eloquent here. This makes sense as in a future release, I’ll be completely removing the SlackUser model, so if I relied on Eloquent for the migration, it would throw an error if I ever deleted that class. Here’s what the migration looked like:

DB::table('slack_users')->get()->map(
	function ($slackUser) {
		$userId = $slackUser->user_id;
		$slackUser = Arr::except((array) $slackUser, ['id', 'created_at', 'updated_at', 'user_id']);
		$user = User::findOrNew($userId);
		if (!$user->exists) {
			$user->name = $slackUser['slack_user_id'];
			$user->password = Hash::make(random_bytes(20));
		}
		$user->fill($slackUser);
		$user->save();
	}
);

This is all pretty straightforward. For each SlackUser, check if they have a User model already, and if not, create a new User and give them a random password. My app doesn’t actually use passwords for authentication, instead relying solely on Slack Oauth, so the password field is irrelevant. In the future I may want to allow for other auth methods, so I left it for the sake of simplicity.

After all of the users were migrated, I could then go about the business of updating other models that used the SlackUser as a foreign key. In my case, I had messed up in the original migrations and not enforced those foreign keys, but if they are enforced in your app, you’ll need to drop the foreign key before you go about migrating all of this data around.

$table->dropForeign(['slack_user_id']);

Here is what the migration to change the foreign keys on my Cake model looked like.

DB::table('cakes')->get()->map(
	function ($cake) {
		$giver = DB::table('slack_users')->select('slack_user_id')->where('id', '=', $cake->giver_id)->get()->first();
		$giver = User::where('slack_user_id', '=', $giver->slack_user_id)->withTrashed()->first();
		$target = DB::table('slack_users')->select('slack_user_id')->where('id', '=', $cake->target_id)->get()->first();
		$target = User::where('slack_user_id', '=', $target->slack_user_id)->withTrashed()->first();
		DB::table('cakes')->where('id', $cake->id)->update(['giver_id' => $giver->id, 'target_id' => $target->id]);
	}
);

Again, notice that I’m not using Eloquent to access the SlackUser model, instead relying on the DB:class facade. I’m free to delete the SlackUser::class at anytime now!

All of this code was added to the up() method of my migration, and I carefully reversed all of the column changes for the down() method. One thing I did not do in the down method, was remigrate any data. I figured if the deploy went so bad that I needed to do that, then I would be better off restoring the database entirely from a backup instead. The down() changes I made were purely so I could migrate up/down for tests, which weren’t reliant on any database values anyway.

That brings me to another pain point: tests! 90% of my tests had to be updated since they mostly relied on the SlackUser. I started with one test file at a time, running the entire file first, then each failing test. I generally changed any instance of SlackUser to User first and then saw what broke. The first few were painful as there were references to relationships that needed to be updated. Often times while doing this, I would catch something that needed to be updated in my migration as well.

Eventually, most of the methods from SlackUser were migrated to User and all of the relationships were updated. Views were the last thing to be checked and I even managed to add a few missing tests based on failures I found while manually browsing the site locally.

// One of my newly added tests!
/** @test */
public function a_user_can_view_the_perk_redemption_page() //phpcs:ignore
{
	$this->withoutExceptionHandling();
	$user = factory(User::class)->create([
	  'is_owner' => true
	]);

	// Make fake users for the manager selection.
	factory(User::class, 5)->create([
	  'team_id' => $user->team->id
	]);

	$this->actingAs($user);

	$user->team->perks()->create([
	  'title' => 'an image perk',
	  'cost' => 100,
	  'image_url' => 'https://perk-image.com/perk.jpg'
	]);

	$this->assertCount(1, Perk::all());

	$res = $this->get(route('redeem-perk-create', Perk::first()));
	$res->assertOk();

	// Check for all of the names on the page. (manager selection for perk redemption)
	$res->assertSee([...User::all()->map->name]);
}

In the end, the Github PR had 57 changed files! A huge undertaking by any standard. I would venture a guess that those 57 files represent 80-90% of all of the code I had written for the app.

Overall, I’m happy I did this, as the logic surrounding users is much simpler to understand. I also got the opportunity to try out some new things and learn a bit more about the built in DB facade. I’m still kinda intimidated by SQL in general, but I’m getting more courageous every time I tackle one of these projects. Backups are still really important though! 😉

Conclusion

If you have any questions, feel free to reach out to me on Twitter or leave a comment!

How to use Tailwind CSS v1.0 with Laravel Mix

Now that Tailwind CSS is approaching version 1.0, I wanted to go ahead and start using it on some projects that will be launching in the next few months. It seems the API is stable, so now seems like as good a time as any to document how to get Tailwind up and running with a new Laravel project.

Install Dependencies

All you need to do is run a few simple commands.

Install Tailwind 1.0 Beta

yarn add -D tailwindcss@next

Install Laravel Mix Tailwind

yarn add -D laravel-mix-tailwind

Generate Tailwind config file

yarn tailwind init

This is where all of your modifications to Tailwind will live. Check the official documentation for specifics on which keys to add/override depending on your needs.

Replace webpack.mix.js

In your webpack.mix.js file, replace it with this snippet to mimic the default behavior in a new Laravel project.

const mix = require('laravel-mix');
require('laravel-mix-tailwind');

/*
|--------------------------------------------------------------------------
| Mix Asset Management
|--------------------------------------------------------------------------
|
| Mix provides a clean, fluent API for defining some Webpack build steps
| for your Laravel application. By default, we are compiling the Sass
| file for the application as well as bundling up all the JS files.
|
*/

mix.js('resources/js/app.js', 'public/js')
    .sass('resources/sass/app.scss', 'public/css')
    .options({
        postCss: [require('tailwindcss')]
    });

The above snippet will compile Tailwind using the standard app.scss as the base. Be sure to add your Tailwind directives to the top of that file so the utility classes are injected. You can also still use SASS if that’s something you want to take advantage of inside of your custom CSS components.

Running Mix is the same as always.

yarn run watch
// OR
yarn run dev

Updates

If something changes when Tailwind 1.0 is officially released, I’ll try to update this article accordingly. Leave a comment or reach out on Twitter if something needs to be updated or modified.