Today’s article is meant to show you how to go from plain-text tasks to a collection of classes that are meant to work together.
Table of Contents
ToggleWhat is the project about?
The project describes a cron, that was built taking in consideration the folowing steps:
- Read Excel files (the files are imported from another software product).
- Insert data from each row, contained by the Excel file, into a MySQL database.
- Insert statistics into database, regarding the parsing process of the file – these will be refered to as
statistic objects
. - Most important task: make it OOP.
As you can see this description is a bit short, so allow me to go in more detail.
The cron needs to be able to read excel files exported from another IT software. Basically, the client already has a backend application, used for saving data about his products, but needed something to export that data from excel files to a mysql database server.
The reason being that in the future his clients can log into a website and modify their data themselves, without contacting his office.
Now, the fun part!
However, the description above looked a little too brief to me, so I asked myself the following questions:
How easy will it be to add new functionalities to the existing ones?
How easy will it be to enrole the project, from its current state, into a framework? (By the time i wrote this, i’ve already enroled the project into Symfony)
How easy will it be, for a new programmer, to take the project and create a new functionality, without knowing all the details of the project?
Starting from this questions, I digged around, searching for solutions. This is what it helped me: Chain of Responsibility design pattern.
To avoid any ambiguity, this article does not show you how to implement Chain of Responsibility
it describes how to implement the “chain” design pattern. The difference between these two is that the event is treated by all the objects and not only by one, like in the Chain of Responsibility
pattern.
As I previously said, I was inspired by Chain of Responsibility
(I wanted three objects that do things separate ways):
- The first one should scan the Excel data folder (in this directory, I put all the Excel files that needed to be parsed) and create statistic objects for each file.
- The second object should parse those files, take the data and insert it into the MySQL database.
- The third object should insert the data from the statistic objects (objects that describe the files that were parsed) in the database and move the Excel files from the initial directory into the
ExcelParsedFiles
directory.
All good and dandy, but if you translate this into a php
script, you will probably be tempted to do something like this:
<?php // this will take care of the first task $objFinder = new FilesFinder(); // this will return the statistic objects $files = $objFinder->getFiles(); // object for parsing and inserting data from files $objParser = new XLSXParser(); $objParser->insertDataFrom($files); // save the statistics and move the files to excel files runned folder $objStatistics = new Statistics(); $objStatistics->insertStatistics($files);
I’m not a very big fan of this, so, a solution to avoid it, will be to wrap the three objects into a list that executes itself. Let’s take a look at the “final” file that takes care of the request:
// get sequence of classes that are needed for parsing job $chain = ChainBuilder::getInstance()->getChain(); $chain->execute();
To explain the first line, ChainBuilder
is the object responsible for wrapping all three objects (FilesFinder
, XLSXParser
, Statistics
) in just one object (see the bellow print of the object).
FilesFinder Object ( [_successor:protected] => XLSXParser Object ( [_successor:protected] => Statistics Object ( ) ) ) )
Every one of those three classes extend a fourth abstract class, called ChainElement
(you will find the code for this class below) .
<?php /** * Base class for every object part of the sequence * of objects that need to be executed */ abstract class ChainElement { /** * the next object that needs to run * @var ChainElement */ protected $_successor = null; /** * method for setting the next object that needs to be executed * @param ChainElement $objSuccessor */ public function setSuccessor(ChainElement $objSuccessor) { $this->_successor = $objSuccessor; } /** * getter for the next object that needs to be executed * @return ChainElement */ public function getSuccessor() { return $this->_successor; } /** * abstract function that every object part of the sequence * needs to overwrite */ abstract function execute(); /** * method to determine if we have a successor and if we have * call his execute method */ public function executeSuccessor() { if(!is_null($this->_successor) && is_object($this->_successor) === true) { $this->_successor->execute(); } } }
As you can see, the FilesFinder
class, the XSLXParser
class and the Statistics
class need to overwrite the abstract method execute()
from ChainElement
.
Another requirement for this is that, at the end of each execute()
method, the executeSuccessor()
method should be called, in order to do the next part of the job.
To explain even better, objFinder
will make the call, at the end of execute()
method, that will invoke the next object; the objXLSXParser
will run its execute()
method, that will invoke the objStatisctics’s execute()
method.
As a result of this approach, I can always add a new object in the sequence, without compromising the existing code. For example, I can add an objCleaner
(doesn’t matter what it does) at wherever in the sequence, the object will need to extend the ChainElement
and overwrite the execute()
method .
As an addition to this classes, if you will ever use this, I suggest you make another class (mine is called ChainParams
), that will implement the Singleton
design pattern, which should hold the “communication” objects from one ChainElement
to another.
Hope you’ve enjoyed the post!