Posts tagged with spatie

How to create a most popular list with Laravel and Google Analytics

Over at Laravel News Eric L. Barnes posted a new tutorial on how he used our Analytics package to create a list of most popular posts. Great stuff!

Here on Laravel News, I wanted to generate a list of the most popular posts for the past seven days and display the results from most popular to least popular.

To solve this problem I thought of two solutions. The first is to build my own tracking system so I could keep a count and then use it for ordering. However, that could generate a huge amount of data and it seemed like a solution that an analytics tracking service could handle.

As I was fumbling through the Google Analytics API I found a Laravel Analytics package by Spatie.be that allows you to easily retrieve data from your Google Analytics account and it seemed like the best way to solve this problem. Let’s look at how I used it to generate a list of popular posts here on Laravel News.

https://laravel-news.com/2016/09/most-popular-list-laravel-google-analytics/

Read more

A package to easily work with regex in PHP original

by Freek Van der Herten – 2 minute read

PHP offers some functions to work with regular expressions, most notably preg_match, preg_match_all and preg_replace. Unfortunately those functions are a bit hard to use. Take preg_match_all for example, it requires you to pass in an array by reference to get all the matches. When something goes…

Read more

Join 9,500+ smart developers

Every month I share what I learn from running Spatie, building Oh Dear, and maintaining 300+ open source packages. Practical takes on Laravel, PHP, and AI that you can actually use.

No spam. Unsubscribe anytime. You can also follow me on X.

Our packages are now postcardware original

by Freek Van der Herten – 1 minute read

My company has released a lot of PHP and Laravel packages. According to the packagist stats they have been downloaded for a little over 700 000 times. Up until now they've all been free. That is going to change. Our packages are now postcardware. This means that from now on you are required to send…

Read more

Building a Laravel powered Slack bot

At Spatie we've recently introduced a bot to our Slack chat. We've named him Paolo (after the waiter in our favourite Italian restaurant in Antwerp: La Fontanella Da Enzo). Here's a demo of Paolo (the bot) in action.

Behind the scenes Paolo is powered by a Laravel application that responds to all requests Slack is sending to it. In this post I'd like to explain how you can set up your own Laravel powered Slack bot.

General flow

A message in slack that starts with a slash is called a slash command. Whenever you type in a slash command in Slack channel, an http request will be sent to your Laravel app. You have to respond to that command within 3 seconds. Failing to do some will result in an error being displayed in the channel.

After that initial response you're allowed to send multiple delayed responses. But there are some limitations for delayed responses. You may respond up to 5 times within 30 minutes after the user typed in the slash command on the slack channel. Want to know more about slash commands and how they work, then read this excellent article at Slack's API site.

To make responding to Slack a breeze we're going to used the package our team released a few days ago called spatie/laravel-slack-slash-command.

Setting things up

Before you can get started building our Laravel app, you'll need to set up slash command at Slack.com. Head over to the custom integrations page at Slack.com to get started. There click "Slash commands" and on the next page click "Add configuration". On that screen you can choose a name for your Slack command. In our examples we'll use paolo but you can choose anything that Slack allows.

You should now be on a screen that looks like this:

paolo integration settings

In the url field you should type the domain name of your Laravel app followed by one or more segments. In the screenshot we've added a slack segment. You can choose any segment you want. Normally the token field should already be filled. And that's all you need to do at Slack.com.

The next thing you'll need to do is to install the spatie/laravel-slack-slash-command package. Let's pull it in via Composer.

composer require spatie/laravel-slack-slash-command

Next, you must install the service provider:

// config/app.php
'providers' => [
    ...
    Spatie\SlashCommand\SlashCommandServiceProvider::class,
];

The configuration file of the package can be published with:

php artisan vendor:publish --provider="Spatie\SlashCommand\SlashCommandServiceProvider" --tag="config"

This is the contents of the published config file:

return [

    /**
     * Over at Slack you can configure to which url the slack commands must be send.  
     * url here. You must specify that. Be sure to leave of the domain name.
     */
    'url' => 'slack',

    /**
     * The token generated by Slack with which to verify if a incoming slash command request is valid.
     */
    'token' => env('SLACK_SLASH_COMMAND_VERIFICATION_TOKEN'),

    /**
     * The handlers that will process the slash command. We'll call handlers from top to bottom
     * until the first one whose `canHandle` method returns true.
     */
    'handlers' => [
        //add your own handlers here


        //this handler will respond with a `Could not handle command` message.
        Spatie\SlashCommand\Handlers\CatchAll::class,
    ],
];

And with that you're ready to respond to http requests coming from Slack.

Setting up your first command handler

Whenever a user types in a slash command Slack will send an http request to the Laravel app. Next, our package will go over all classes in the handlers key of the config file from top to bottom until the first one whose canHandle method returns true. A handler is a class that is responsible for receiving a request from slack and sending a response back.

Let's create our first handler. Handlers must extend Spatie\SlashCommand\Handlers\BaseHandler and implement the two abstract methods from that BaseHandler: canHandle and Handle.

Here's an example.

namespace App\SlashCommandHandlers;

use Spatie\SlashCommand\Request;
use Spatie\SlashCommand\Response;
use Spatie\SlashCommand\Handlers\BaseHandler;

class Hodor extends BaseHandler
{
    /**
     * If this function returns true, the handle method will get called.
     *
     * @param \Spatie\SlashCommand\Request $request
     *
     * @return bool
     */
    public function canHandle(Request $request): bool
    {
        return true;
    }

    /**
     * Handle the given request.
     * 
     * @param \Spatie\SlashCommand\Request $request
     * 
     * @return \Spatie\SlashCommand\Response
     */
    public function handle(Request $request): Response
    {
        return $this->respondToSlack("Hodor, hodor...");
    }
}

This Hodor class will just respond with Hodor, hodor, ... to every request that is sent to it.

You'll need to register this class in the config file.

// app/config/laravel-slack-slash-command
    'handlers' => [
        App\SlashCommandHandlers\Hodor::class,
        ...
    ], 

Let's see that in action.

A slightly more advanced handler

Let's create a slightly more interesting handler. This one that just repeats the command you've sent to it but only if the text after the command starts with repeat.

namespace App\SlashCommandHandlers;

use Spatie\SlashCommand\Request;
use Spatie\SlashCommand\Response;
use Spatie\SlashCommand\Handlers\BaseHandler;

class Repeat extends BaseHandler
{
    public function canHandle(Request $request): bool
    {
        return starts_with($request->text, 'repeat');
    }

    public function handle(Request $request): Response
    {   
        $textWithoutRepeat = substr($request->text, 7)

        return $this->respondToSlack("You said {$textWithoutRepeat}");
    }
}

Let's register this handler as well.

// app/config/laravel-slack-slash-command

    'handlers' => [
        App\SlashCommandHandlers\Repeat::class,
        App\SlashCommandHandlers\Hodor::class,
        ...
    ],    

If you type in /paolo repeat Hi, everybody in a slack channel now, you'll get a response Hi, everybody back. When you type in /poalo bla bla bla you'll get a response Hodor, hodor... because the Hodor handler is the first one which canHandle-method returns true.

Notice that Spatie\SlashCommand\Request being past in canHandle and handle? It contains all data that's being passed by Slack to our Laravel app. These are it's most important properties:

  • `command`: the command name without the `/` that the user typed in. In our previous example this would be `paolo`.
  • `text`: all text text after the command. In our the example above this would be `repeat Hi, everybody`.
  • `userName`: the Slack username of the person that typed in the command
  • `userId`: the Slack user id of the person that typed in the command
  • `channelName`: the name of the channel where the user typed in the command
  • `teamDomain`: the name of the Slack subdomain. So if your team is on `example.slack.com` this would be `example`.

Customizing your response

By default the response will be sent to the user who typed in the original message. If you want the response to be visible to all users in the channel you can do this:

    public function handle(Request $request): Response
    {
        return $this
           ->respondToSlack("Hodor, hodor...")
           ->displayResponseToEveryoneOnChannel();
    }

There are also many formatting options. Take a look at this response on Slack: attachments

$this->respondToSlack()
    ->withAttachment(Attachment::create()
        ->setColor('good')
        ->setText('This is good!')
    )
    ->withAttachment(Attachment::create()
        ->setColor('warning')
        ->setText('Warning!')
    )
    ->withAttachment(Attachment::create()
        ->setColor('danger')
        ->setText('DANGER DANGER!')
    )
    ->withAttachment(Attachment::create()
        ->setColor('#439FE0')
        ->setText('This was a hex value')
    );

There are many more options to format a message. Take a look at Slacks documentation on attachments to learn what's possible.

Using signature handlers

A console command in Laravel can make use of a signature to set expectations on the input. A signature allows you to easily define arguments and options.

If you let your handler extend Spatie\SlashCommand\Handlers\SignatureHandler you can make use of a $signature and the getArgument and getOption methods to get the values of arguments and options.

Let's take a look at an example.

namespace App\SlashCommandHandlers;

use Spatie\SlashCommand\Request;
use Spatie\SlashCommand\Response;
use Spatie\SlashCommand\Handlers\SignatureHandler;

class SendEmail extends SignatureHandler
{
    public $signature = "paolo email:send {to} {message} {--queue}"

    public function handle(Request $request): Response
    {   
        $to = $this->getArgument('to');

        $message = $this->getArgument('message');

        $queue = $this->getOption('queue') ?? 'default';

        //send email message...
    }
}

Notice that there is no canHandle method present in that class. The package will automatically determine that a command /paolo email:send test@email.com hello can be handled by this class.

Sending delayed responses

Remember that restriction mentioned above about the initial response to a slash command. Your Laravel app only has three seconds to respond otherwise an error message will be shown at Slack. After that initial fast response you're allowed to send 5 more responses in the next 30 minutes for the command. These responses are called "delayed responses". We're going to leverage Laravel's queued jobs to send those delayed responses. Please make sure that you've set up a real queue driver in your app, it needs to be something other than sync.

Imagine you need to call a slow API to get a response for a slash command. Let's first create a handler that will send the initial fast response.

namespace App\SlashCommandHandlers;

use Spatie\SlashCommand\Request;
use Spatie\SlashCommand\Response;

class SlowApi extends BaseHandler
{
    public function canHandle(Request $request): bool
    {
        return starts_with($request->text, 'give me the info');
    }

    public function handle(Request $request): Response
    {
        $this->dispatch(new SlowApiJob());

        return $this->respondToSlack("Looking that up for you...");
    }
}

Notice that we're dispatching a job right before sending a response. Behind the scenes Laravel will queue that job.

This is how that SlowApiJob would look like.

namespace App\SlashCommand\Jobs;

use Spatie\SlashCommand\Jobs\SlashCommandResponseJob;

class SlowApiJobJob extends SlashCommandResponseJob
{
    // notice here that Laravel will automatically inject dependencies here
    public function handle(MyApi $myApi)
    {
        $response = $myApi->fetchResponse();

        $this
           ->respondToSlack("Here is your response: {$response}")
           ->send();
    }
}

Notice that, unlike in the Handlers the response is not returned and that send() is called after the respondToSlack-method.

With this in place a quick response Looking that info for you... will be displayed right after the user typed /your-command get me the info. After a little while, when MyApi has done it's job Here is your response: ... will be sent to the channel.

Some useful handlers

The previous examples of this post were quite silly. You'll probably never going to use to handlers in your bot. Let's review a real life example. Our Poalo bot can lookup dns records for a given domain. This is how that looks like in a Slack channel.

This is the actual class that we use in our bot:

namespace App\SlashCommandHandlers;

use Spatie\SlashCommand\Attachment;
use Spatie\SlashCommand\AttachmentField;
use Spatie\SlashCommand\Handlers\SignatureHandler;
use Spatie\SlashCommand\Request;
use Spatie\SlashCommand\Response;

class Dns extends SignatureHandler
{
    protected $signature = 'paolo dns {domain}';

    /**
     * Handle the given request. Remember that Slack expects a response
     * within three seconds after the slash command was issued. If
     * there is more time needed, dispatch a job.
     *
     * @param Request $request
     *
     * @return Response
     */
    public function handle(Request $request): Response
    {
        $domain = $this->getArgument('domain');

        if (empty($domain)) {
            return $this->respondToSlack("You must provide a domain name.");
        }

        $sanitizedDomain = str_replace(['http://', 'https://'], '', strtolower($this->getArgument('domain')));

        $dnsRecords = dns_get_record($sanitizedDomain, DNS_ALL);

        if (!count($dnsRecords)) {
            return $this->respondToSlack("Could not get any dns records for domain {$domain}");
        }

        $attachmentFields = collect($dnsRecords)->reduce(function (array $attachmentFields, array $dnsRecord) {
            $value = $dnsRecord['ip'] ?? $dnsRecord['target'] ?? $dnsRecord['mname'] ?? $dnsRecord['txt'] ?? $dnsRecord['ipv6'] ?? '';

            $attachmentFields[] = AttachmentField::create('Type', $dnsRecord['type'])->displaySideBySide();
            $attachmentFields[] = AttachmentField::create('Value', $value)->displaySideBySide();

            return $attachmentFields;
        }, []);

        return $this->respondToSlack("Here are the dns records for domain {$domain}")
            ->withAttachment(Attachment::create()
                ->setColor('good')
                ->setFields($attachmentFields)
            );
    }
}

In order to get home every member of our team needs to bike a bit. That's why we've also created a command to display a rain forecast. This is what happens when /paolo rain is typed in our slack channels.

This is the class responsible for creating that response.

namespace App\SlashCommandHandlers;

use Spatie\SlashCommand\Attachment;
use Spatie\SlashCommand\Handlers\SignatureHandler;
use Spatie\SlashCommand\Request;
use Spatie\SlashCommand\Response;

class Rain extends SignatureHandler
{
    protected $signature = 'paolo rain';

    /**
     * Handle the given request. Remember that Slack expects a response
     * within three seconds after the slash command was issued. If
     * there is more time needed, dispatch a job.
     *
     * @param Request $request
     *
     * @return Response
     */
    public function handle(Request $request): Response
    {
        return $this
            ->respondToSlack("Here you go!")
            ->withAttachment(
                Attachment::create()->setImageUrl('http://api.buienradar.nl/image/1.0/radarmapbe?width=550')
            );
    }
}

In closing

The spatie/laravel-slack-slash-command package makes is it easy to let a Laravel app respond to a slash command from Slack. If you start using the package, let me know in the comments below what your bot can do. And if you like our package, take a look at this list of Laravel packages we've previously released to see if we've made something that can be of use to you.

Read more

A package to log activity in a Laravel app original

by Freek Van der Herten – 4 minute read

In your apps there's probably a lot going on. Users log in and out, they create, update and delete content, mails get sent and so on. For an administrator of an app these events provide useful insights. In almost every project we make at Spatie we log these events and show them in the admin-section…

Read more

A package to determine which track a last.fm user is playing original

by Freek Van der Herten – 1 minute read

If you've been reading the last couple of posts on this blog then you'll know we're busy building a dashboard. I think it's fair to say that everyone at Spatie is music addict. We have the unwritten rule that whoever who comes in first at the office is DJ for the day. One of the things we'd like to…

Read more

Starting a business with Laravel Spark

Christopher Pitt is starting a new business with Spark. Over at the Sitepoint blog he has posted a tutorial on how to get started with Taylor's latest creation.

I am really excited about Laravel Spark. By the time you read this, there will probably be a multitude of posts explaining how you can set it up. That’s not as interesting to me as the journey I’m about to take in creating an actual business with Spark!

I have often wanted a way to quickly and painlessly transfer this application state from one server to another, and make automated offsite backups. So I’m going to set that up for myself, and perhaps others will find it useful enough to pay for it.

http://www.sitepoint.com/starting-a-business-with-laravel-spark/

At Spatie, we're also in the process of creating or first SaaS based on Spark. That's why our package output will slow down a bit. It's too early to give any specifics on what we're building but I can already tell you that it's something very simple that we need at the company ourselves. Thanks to Spark we can open our solution up to other users. You'll hear more about it in a month or so.

Still on the topic of Spatie, maybe you've noticed that our company website is in Dutch. So most of you can't understand a single word on it. That's going to change in the near future: besides building a SaaS our team is in the process of creating a new website. This time there will be an English version.

Exciting times!

Read more

Laravel medialibrary hits v4 original

by Freek Van der Herten – 2 minute read

Today we tagged a new major version of laravel-medialibrary. In case you're not familiar with this package here's a quick rundown of what it can do. The package can associate all sorts of files with Eloquent models. It provides a simple, fluent API to work with. Here's a quick example: $newsItem =…

Read more

Making Eloquent models translatable original

by Freek Van der Herten – 2 minute read

Spatie, the company where I work, is located in Belgium. Although our country is quite small, there are three official languages: Dutch, French and German. That's why nearly all of our projects are multilingual. In most projects we used to rely on Dimitris Savvopoulos popular translatable package to…

Read more

Publishing package assets the right way original

by Freek Van der Herten – 2 minute read

At Spatie we do not only create a lot of Laravel packages, but we use also use a bunch of existing ones. In this post I'd like to give a quick hint to our fellow package developers. In the readme's of packages you'll often find an instruction like this to publish it's assets: php artisan…

Read more

Say goodbye to manually creating a robots.txt file original

by Freek Van der Herten – 1 minute read

If you don't want a site to be indexed by search engines you must place a robots.txt file. Typically you don't want anything indexed except production sites. Today Spatie released a new package called laravel-robots-middleware. It was coded up by my colleague Sebastian. Instead of you having to…

Read more

A package to extract text from a pdf original

by Freek Van der Herten – 1 minute read

For a project I needed to extract some text from a pdf. Although there were already a few packages that could to this, I still created spatie/pdf-to-text because I wanted something that was really easy to use. And also because it's fun creating packages. Under the hood a utility called pdftotext is…

Read more

Easily convert images with Glide

Glide is an easy to use image manipulation library by Jonathan Reinink. It was bumped to version 1.0.0 a few days ago. Glide can created optimized images on the fly by levering url's such as /img/users/1.jpg?w=300&h=400&fit=crop. Take a look at the example in the Glide documentation to know more.

I think Glide provides a very nice API to create image manipulations. Unfortunately it isn't very easy to use the API to generate an image using code. So I created a little package for that called laravel-glide. All new major versions of Spatie packages will require PHP 7, laravel-glide is no exception to this.

Here's an example of how to create a greyscale version image with a maximum width of 50 pixels.

GlideImage::create($pathToImage)
    ->modify(['filt'=>'greyscale', 'w'=> 50])
    ->save($pathToWhereToSaveTheManipulatedImage);

Take a look at Glide's image API to see which parameters you can pass to the modify-method.

Read more

A PHP 7 / Laravel package to create slugs original

by Freek Van der Herten – 2 minute read

Spatie, the company where I work, recently released a Laravel package called laravel-sluggable. It automatically creates unique slugs when saving a model. To install the package you just need to put the provided Spatie\Sluggable\HasSlug-trait on all models that have slugs. The trait contains an…

Read more

Building a crawler in PHP original

by Freek Van der Herten – 4 minute read

When Spatie unleashes a new site on the web we want to make sure that all, both internal and external, links on it work. To facilitate that process we released a tool to check the statuscode of every link on a given website. It can easily be installed via composer: composer global require…

Read more

On open sourcing Blender original

by Freek Van der Herten – 3 minute read

At Spatie we use a homegrown Laravel template called Blender. It's being used on nearly all our projects. When starting with a greenfield project we take a copy of Blender and make project specific changes in that copy. We built it because we want maximum flexibility and don't want to be hampered by…

Read more

URL signing in Laravel

The project I'm currently working on will have to send out mails to all its users on a regular basis. It's not a newsletter: the contents of each mail will be very specific to each user. The mail also should contain a link to unsubscribe the user from simular future mails.

The link could look like this: https://myapp.com/unsubscribe. Clicking on the link would direct the user to a login page. After the user has logged in the unsubscribe can be automatically performed. In my mind requiring the user to login first in order to unsubscribe from something isn't very user friendly.

This can be improved by adding the id of the user to the link. Here's what that could look like: https://myapp.com/user/1/unsubscribe. With this link you the app can unsubscribe the user with id 1 in one go. That'll work, but it's not very secure. Unsubscribe links for all other users can be easily guessed. Such links can be made more secure by adding a signature and an expiry date on them.

My colleague Sebastian coded up a Laravel package to create signed url's with a limited lifetime. Here's example where the url gets signed and made valid for only one day:


echo UrlSigner::sign('https://myapp.com/user/1/unsubscribe', 1);

This outputs an url that looks like: https://myapp.com/user/1/unsubscribe?expires=1123690544&signature=93e02326d75

The validate-method can be used the determine if a signed url is (still) valid:


$isValidUrl = UrlSigner::validate($theSignedUrlInTheExampleAbove);

The signature is calculated using the original url itself, the expiration date and a secret string that's specific to your project. When a malicious user tries to change any part of the url the signature won't match up.

I'm assuming that the most common use case of signing url's is to protect routes. The package supplies a middleware that protects routes from invalid signed url's. In the following example only requests with a valid signed url will hit the controller:


Route::get('unsubscribe', ['middleware' => 'signedurl', 'uses => 'UserController@unsubscribe']);

If you're interested in using the package, take a look at it on GitHub: https://github.com/spatie/laravel-url-signer

There's also a framework agnostic version: https://github.com/spatie/url-signer

E-mails can be intercepted and are never 100% secure. Bearing that fact in mind you should never use this kind of link for any destructive action.

EDIT: Some fellow developers pointed out that I could also obfuscate the id in the url. Here are two good libraries to do that:

When using obfuscation of the id this url `https://myapp.com/user/1/unsub `would become something like: `https://myapp.com/user/kwxgqu5w/unsub`

And sure enough, the unsubscribe links of other users cannot be easily guessed. A small downside however is that the url becomes less readable. The big disadvantage is that the url will remain valid forever. As these links through an unsafe medium I think it's a good idea to give them a limited lifetime. Signing an url will do that.

Read more

Our open source software original

by Freek Van der Herten – 1 minute read

The past few months my colleagues and I invested quite some time on creating open source software. Because there now are a lot of packages under the Spatie-vendor name, we decided to put a nice overview on our website. Obviously these packages benefit the community, but there are a lot of advantages…

Read more

Common string functions

When working on projects I found myself, over time, needing the same string functions over and over again. Things like how to shorten a string but still let it end on entire word, replace the last occurrence of string in a string, ...

Instead of keeping these functions in a helper file locally in the project, I made a package out of it. It was a good opportunity to spice it up with bit of OO to make them chainable. An example:

[code] // outputs "MIDDLE" echo string('StartMiddleEnd')->between('Start', 'End')->toUpper();



In addition to it's own methods, the package provides <a href="https://github.com/spatie/string#integration-with-underscorephp">an integration</a> with <a href="https://github.com/Anahkiasen/underscore-php">Maxime Fabre's underscore package</a>.

You are very welcome to submit pull requests to add functions that you feel are missing. Be sure to include some unit tests to ensure everything working as intended.

Granted,  it isn't the most sexy package in the world, but it sure is handy.

<a href="https://github.com/spatie/string">https://github.com/spatie/string</a>

Read more