This blog post is Human-Centered Content: Written by humans for humans.
We now have somewhere to put our package code and a functional Laravel app to test it in live. There are skeleton packages that act as templates like this one, but there’s a benefit to doing it manually at least once. It’s like being forced to do math by hand before being allowed to use a calculator. It gives you a sense of what things should look like before relying on tools to get you there, making things less of a black box.
First, let’s add a class to the src directory that the composer config generator created. Mine looks like this:
<?php namespace InterWorks\ThoughtSpotREST; class ThoughtSpotREST { public function __construct() { dd(‘Hello, World!’); } }
I’ve named my class ThoughtSpotREST and added a constructor. A session must be started when making ThoughtSpot REST API calls, which requires retrieving a token or session cookies for subsequent calls. Keeping track of the session can be done in a few ways, but making the class object-oriented made the most sense to me. That way, the session stuff can be retrieved in the constructor and be ready to use for subsequent method calls. Right now, the constructor only dd’s (die and dumps) out the string “Hello, World!” to the screen. This verifies the code is being fired.
Now, where to put this? My reflex is to put this in a route. Routes are central to how a Laravel app directs users to specific pages, it’s basically the first code the app hits (there’s other code it hits first, but close enough for our purposes). If you open the routes/web.php, there’s already a route for the base URL that loads the welcome view. I know this code is fired when I load http://localhost:8000 so this is a great spot for some test code.
I’ll tweak the routes file to look like this:
<?php use Illuminate\Support\Facades\Route; use InterWorks\ThoughtSpotREST\ThoughtSpotREST; Route::get('/', function () { $ts = new ThoughtSpotREST(); return view('welcome'); });
I’ve added a use statement for the class we just made and a line to instantiate a ThoughtSpotREST object. This fires the constructor which dd’s out my test string to the screen after reloading the page. That means my code is firing as expected!
We’ll need developers using this package to be able to input their ThoughtSpot cluster information so the package knows where to send API calls. The convention for this is using environment variables through a config file. You could just use environment variables, but the config files allow the values to be cached to improved performance.
I’ve added a config directory on the same level as the src directory and a file within it called thoughtspotrest.php. I’ve added entries like this one for each required config variable:
<?php return [ /* |--------------------------------------------------------------------------- | ThoughtSpot URL |--------------------------------------------------------------------------- | | This value is used to specify the ThoughtSpot cluster to make REST API | calls against. This value should be the base URL of the cluster. Trailing | slashes will be ignored. | */ 'url' => env('THOUGHTSPOT_REST_URL'), ];
The comment block above the url config is the standard format that Laravel uses. You certainly don’t need to do that, but following convention makes your code much easier for others to read and help build upon.
Note that the config value points to the THOUGHTSPOT_REST_URL environment variable. Developers will be able to add that value to their .env file in their app and the package will pick it up here. When your package classes pull values using config() instead of env() it allows Laravel to cache the values for speedier retrieval.
We need to register this config file so the app’s that use this package know to look for it. The first step for registration is adding a service provider called ThoughtSpotRESTServiceProvider.php in the src directory. A service provider is a central place to register bindings, singletons, event listeners, middleware and other services within the application’s service container. It allows you to bootstrap and configure application-wide services, ensuring they are available when needed. In simpler terms, the service providers are a setup guide for the app. They tell Laravel what tools are needed so they’re ready to use.
Below is the entirety of the ThoughtSpotRESTServiceProvider.php file:
<?php namespace InterWorks\ThoughtSpotREST; use Illuminate\Support\ServiceProvider; class ThoughtSpotRESTServiceProvider extends ServiceProvider { /** * Register the application services. */ public function register(): void { $this->registerConfig(); } /** * Merges the configuration file with the application's configuration. */ public function registerConfig(): void { $config = __DIR__ . '/../config/thoughtspotrest.php'; $this->publishes([$config => config_path('thoughtspotrest.php')]); $this->mergeConfigFrom($config, 'thoughtspotrest'); } }
The action is all happening in the registerConfig function which tells Laravel, “Hey, there’s a config file here.” Now that the provider file exists, we need to tell Laravel to look for that. We can add the following block to the package’s composer.json file:
"extra": { "laravel": { "providers": [ "InterWorks\\ThoughtSpotREST\\ThoughtSpotRESTServiceProvider" ] } },
Now when the package is installed Laravel will know to look for this service provider on boot which is how the app will know to look for the config file. This may feel like a lot of setup but, fortunately, it’s a one-and-done thing. If we ever want to add more to our service provider like middleware or events, we can simply add them to the service provider file.
Before wrapping up, I’d like to mention a couple design choices I made while creating this package:
- There were 87 REST API endpoints available when I made this. Sticking 87 functions to handle each in a single file would be technically fine but isn’t a great way to organize code. Instead, I created traits named after each endpoint category like authentication, orgs, metadata, etc. This made the code much easier to follow while keeping the syntax for the end user simple.
- Each of those aforementioned 87 endpoints have a combination of possible arguments. If I manually made each API argument a function parameter the code would be very complex. Instead, I added an $args parameter where relevant so users could define those as needed. The function then just passes right on to the API. This also saves me from an infinite amount of maintainability issues when ThoughtSpot changes their API arguments. End-users won’t have to wait for me to update the package, they can just update their code.