Trello CLI

Trello CLI PHP Symfony

Trello is an excellent tool to organize our work. After I have been laid off because of COVID-19, it has served me very well to organize my job search, the homeschooling of my kids, and my professional preparation.

Reading about different topics of Software Development is part of my daily routine. One of my favorites sources is ThoughtWorks. Usually, their articles contain a lot of useful links to go deeper into the content of the article. Normally I take some of the most interesting links, copy them, and create a Trello's Card with a checklist. It was a very repetitive process. That is why I created a Trello CLI that allows extract links from any web page, and it creates a new Trello's card with a checklist where the check items are the links extracted. I created a public repo with the code here: https://github.com/WebCu/trello-cli.

Development Environment

Docker is actually my default option to create my development environments. I have a setup configured already for PHP that includes, xDebug, composer, git, and others extensions. You can check it here: https://github.com/WebCu/trello-cli/blob/master/.docker/Dockerfile.

Development tools

I have been working with Symfony framework for a while. It's a very complete framework. One of its main strengths is the quality of its components. To the development of this POC, I decided to combine some of its components but without using the framework itself.

Composer

Composer is the default tool in PHP to handle the dependency management.

To specify from where are we going to load the classes, and the namespace that we want to use, we add the option autoload to the composer.json of the project. Ex:

"autoload": {
    "psr-4": {
        "Trello\\CLI\\": "src/"
    }
}

We must include the composer autoload in our files to be able to use the classes. Ex:

require_once __DIR__.'/vendor/autoload.php';

Symfony Console

This project is a CLI and it exists already a nice component to help us: Symfony Console. It's easy to use. We create a new application, add to it the command that we want to handle, and run the application. Ex:

$application = new Application();
$application->add(new CreateLinkCardCommand($trelloApi, $webCrawler));
$application->run();

Commands

Commands are defined in classes extending Command. The main methods are: configure and execute. The last one is mandatory. To property defaultName is the one we're going to use to invoke the command. To learn more about the Commands check: Console Commands.

We invoke our current command like this:

php index.php trello-cli:create-link-card

Helpers

Symfony Console comes with a set of very useful helpers: Console Helpers. For this project I used the Question Helper, and the Progress Bar Helper.

Question Helper

The Question Helper provides functions to ask the user for more information. It is included in the default helper set, which you can get by calling getHelperSet():

$helper = $this->getHelper("question");

In our case we used it to ask the user to select the Board, and the List where he wants to add the Card, as well the name of the Card, from where extract the links, etc.

Progress Bar Helper

When executing longer-running commands, it may be helpful to show progress information, which updates as your command runs. The Progress Bar Helper provides that functionality and allows a great degree of customization.

Here we used it to show to the user the progress related to the creation of the check items.

Symfony Http Client

To extract information from a web page, and communicate with Trello's API we need a way to talk the HTTP language. Symfony Http Client help us with that task.

According to its documentation: The HttpClient component is a low-level HTTP client with support for both PHP stream wrappers and cURL. It provides utilities to consume APIs and supports synchronous and asynchronous operations.

It's easy to use. First, we need to create a new instance

$httpClient = HttpClient::create();

and after start creating our requests, ex:

$response = $httpClient->request(
    "POST",
    "https://api.trello.com/1/checklists",
    ["query" => array_merge($defaultQuery, ["idCard" => $idCard])]
);

$content = $response->toArray();

Symfony Dot Env

The way we handle our secrets variables is very important. Symfony Dot Env help us with that issue. It parses .env files to make environment variables stored in them accessible via $_ENV or $_SERVER.

It's a good idea create a .env.dist file where you put just the name of the env variables your system need and add it to your repo. At the same time add your .env file to your .gitignore file to avoid adding your secrets to your repo.

This is how I used in this project:

$dotenv = new Dotenv();
$dotenv->load(__DIR__.'/.env');

$httpClient = HttpClient::create();

$trelloApi = new TrelloApi($httpClient, $_ENV['TRELLO_API_KEY'], $_ENV['TRELLO_API_TOKEN']);

Symfony Dom Crawler

OK, now we have our Console ready, our HTTP Client is making correctly the requests, and our secrets are safe. How can we extract the links from the web pages we want? Symfony Dom Crawler to the rescue! The DomCrawler component eases DOM navigation for HTML and XML documents.

After have received a response from the web page we want to extract the links. We create a crawler with the content of the response:

$response = $this->httpClient->request(
    "GET",
    $url
);
$content = $response->getContent();
$crawler = new Crawler($content);

Now we filter the content by links and return and array with them:

$links = $crawler->filter("a")->each(
    fn (Crawler $aCrawler) => [
        "text" => $aCrawler->text(),
        "href" => $aCrawler->attr("href")
    ]
);

This component is really powerful. I recommend you go to check its documentation if you need to do some more specific job with it.

Code Standards, Mess Detectors, and Static Analysis

It's very important to have a set of predefined rules to evaluate the quality and the code standards of our code. Luckily already exists a group of tools to help us with this task. Here is a list of the ones I used:

Development Decisions

While we are developing a solution big or small we're all the time taking decisions. It's important to take notes of them, to have a clear idea of what was happening in our minds later on when we need to revisit our code.

Single quotes or double quotes?

Like I used a lot variable interpolation, I prefer use double quotes. Ex:

"body" => ["name" => "[{$text}]({$href})"],

Dependencies

I created the services TrelloApi and WebCrawler to handle all the Trello's interactions and all the web pages extraction respectively, following the Single-responsibility Principle (SRP). We could create them directly inside of the command, but it's a better practice pass them like dependencies to the command, that way we can change easily when testing the app or if whe decide use other implementation. An even better solution is to pass to the command an interface, that way it doesn't rely on a specific implementation.

public function __construct(TrelloApi $trelloApi, WebCrawler $webCrawler)
{
    $this->trelloApi = $trelloApi;
    $this->webCrawler = $webCrawler;

    parent::__construct();
}   

Arrow functions

This feature appeared in PHP 7.4. It has many advantages like: allows us to access directly the variables defined in the outer scope of the function, you write less code, and others. For more details check this great guide: Short Closures in PHP. Like everything this feature is not a silver bullet. I like it, but it makes difficult to test the code inside of it. You should evaluate each specific case to decide if use them or not.

To Do

component

References