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.
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.
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 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';
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 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
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.
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.
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.
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();
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']);
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.
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:
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.
Like I used a lot variable interpolation, I prefer use double quotes. Ex:
"body" => ["name" => "[{$text}]({$href})"],
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();
}
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.
component