IllicoDB3 - Database Creation
Here is part 2 of this article. As promised, we will start with some code, code that will be as close as possible to an ideal formulation tailored to the use I envision for the classes, methods, and functions to be designed: it’s as if I am putting myself in the shoes of those who will have to USE the IllicoDB3 class, for whom ease of use will be paramount. Therefore, I FIRST focus on creating my examples BEFORE creating my classes. This corresponds to a TDD approach - Test Driven Development.
The examples are gathered in a single file, here test.illicodb3.php, and are developed sequentially: I start with the basics of the basics, and then I gradually expand the range of examples to cover what I intend to develop. At this point, there is no IllicoDB3 class yet.
When I run the first test, boom... it crashes! That's normal: no class has been developed; PHP tells me that the IllicoDB3 class was not found.
Fatal error: Uncaught Error: Class "IllicoDB3" not found in test.illicodb3.php:71 Stack trace: #0 {main} thrown in test.illicodb3.php on line 71
Okido. So ... the next move seems to start developing the IllicoDB3 class. I'll create an empty one. Once created, I run test.illicodb3.php again, and it crashes again. This time it’s not because the class was not found, but rather because the create() method was not found (this is my first example: before being able to use a DB, it must be created)."
Fatal error: Uncaught Error: Call to undefined method trql\db\IllicoDB3\IllicoDB3::create() in test.illicodb3.php:71 Stack trace: #0 {main} thrown in test.illicodb3.php on line 71
What is this line 71 that causes these crashes?
It is similar to ...
if ( $success = IllicoDB3::create( $DBName,$folder,$tables ) )
echo "<p>The '{$DBName}' DB was successfully created</p>\n";
else
echo "<p>The '{$DBName}' DB COULD NOT BE CREATED</p>\n";
A this point, it does not matter to see the details of $DBName or $folder or $tables: simply, the create method does not exist! Lets create one:
class IllicoDB3
/*-----------*/
{
static public function create()
{
return ( true );
}
}
Very well, the fake method for creating a DB is created : it returns always true.
I give my example another try and, oh miracle, here's what I obtain.
The 'firstDB' DB was successfully created
LINE: 76 - Perf: 0,000346 sec to create a new DB
My development is indeed driven by my tests, which thus determine the code more than the code determines the tests. This is the method I propose to you here. It's called TDD which stands for Test Driven Development. Not all your tests must be written before the code of the class, but I strongly advise to specify your tests before you write any further code in your class.
As we progress in the creation of the code of the class, we will first extend our tests, even to a point at which they can be be considered acceptance tests.
So, in this TDD development perspective, the first operation we will address is the creation of a very simple DB that will contain only one table. We will name this DB "firstDB" and the table it will contain "firstTable." This table will correspond to the schema proposed in the first article, namely..."
Let's anticipate a little and see what the structure of the DB should look like...
<?xml version="1.0" encoding="UTF-8"?>
<database>
<tables>
<table name="firstTable">
<field name="field1" length="6" type="string" />
<field name="field2" length="4" type="string" />
<field name="field3" length="7" type="string" />
<field name="memo1" length="4" type="memo" />
<field name="memo2" length="4" type="memo" />
</table>
</tables>
</database>
The XML structure presented here corresponds to the structure of firstDB. However, no code has yet been created to create this structure, which is only to be expected as this is precisely what the static create() method should be doing, as it is currently empty and simply returns a logical “true” value in all cases. Normal, stupid!
OK. Let's be more ambitious and create the necessary code BUT first, let's see how we specify the tables to be inserted into the DB structure. Let's have a look at $DBName, $folder and $tables.
$tables = [];
$tables[] = array( 'name' => 'firstTable' ,
'fields' => array( array( 'name' => 'field1' ,
'type' => 'string' ,
'length' => 6 ,
) ,
array( 'name' => 'field2' ,
'type' => 'string' ,
'length' => 4 ,
) ,
array( 'name' => 'field3' ,
'type' => 'string' ,
'length' => 7 ,
) ,
array( 'name' => 'memo1' ,
'type' => 'memo' ,
'length' => 4 ,
) ,
array( 'name' => 'memo2' ,
'type' => 'memo' ,
'length' => 4 ,
) ,
) ,
);
$DBName = 'firstDB';
$folder = '/home/vaesoli/central-catalog';
Note: the folder will depend on your needs. It must be a folder you have access to, otherwise the attempt to create the DB will miserably fail. Let's get back of the example:
领英推荐
echo "<h1>Tests of IllicoDB3</h1>";
if ( true ) /* Create a new DB : firstDB */
{
echo "<h2>Create a DB</h2>";
$tables = [];
$tables[] = array( 'name' => 'firstTable' ,
'fields' => array( array( 'name' => 'field1' ,
'type' => 'string' ,
'length' => 6 ,
) ,
array( 'name' => 'field2' ,
'type' => 'string' ,
'length' => 4 ,
) ,
array( 'name' => 'field3' ,
'type' => 'string' ,
'length' => 7 ,
) ,
array( 'name' => 'memo1' ,
'type' => 'memo' ,
'length' => 4 ,
) ,
array( 'name' => 'memo2' ,
'type' => 'memo' ,
'length' => 4 ,
) ,
) ,
);
$DBName = 'firstDB';
$folder = '/home/vaesoli/central-catalog';
$t1 = microtime( true ); /* Start measuring perf. */
if ( $success = IllicoDB3::create( $DBName,$folder,$tables ) )
echo "<p>The '{$DBName}' DB was successfully created</p>\n";
else
echo "<p>The '{$DBName}' DB COULD NOT BE CREATED</p>\n";
$t2 = microtime( true ); /* Start measuring perf. */
echo "<p>LINE: <b>" . __LINE__ . "</b> - Perf: ",number_format( $t2-$t1,6,',', '.' )," sec to create a new DB</p>\n";
echo "<hr />";
}
This is what happens to the code of the static create() method.
static public function create( string $DBName,string $folder,array $tables )
{
$DBFile = "{$folder}/{$DBName}" . ILLICODB3_EXTENSION;
$szXML = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
$szXML .= "<database>\n";
$szXML .= SP_4 . "<tables>\n";
foreach ( $tables as $key => $tableData )
{
$szXML .= SP_8 . "<table name=\"{$tableData['name']}\">\n";
foreach( $tableData['fields'] as $field )
{
if ( ( $iLength = self::fieldLength( $field['type'] ) ) === -1 )
$iLength = $field['length'];
$szXML .= SP_12 . "<field name=\"{$field['name']}\" length=\"{$iLength}\" type=\"{$field['type']}\" />\n";
} /* foreach( $aData['fields'] as $field ) */
$szXML .= " </table>\n";
} /* foreach ( $aTables as $name => $aData ) */
$szXML .= SP_4 . " </tables>\n";
$szXML .= "</database>\n";
return ( self::strToFile( $szXML,$DBFile ) );
At this stage, there are a number of comments to be made about this code:
Here are the definitions in question and the static methods:
defined( 'ILLICODB3_CLASS_VERSION' ) or define( 'ILLICODB3_CLASS_VERSION','0.1' );
/* Useful definitions */
defined( 'SP_4' ) or define( 'SP_4',' ' );
defined( 'SP_8' ) or define( 'SP_8',SP_4 . SP_4 );
defined( 'SP_12' ) or define( 'SP_12',SP_4 . SP_8 );
defined( 'ILLICODB3_EXTENSION' ) or define( 'ILLICODB3_EXTENSION','.illicoDB3' );
/* Define range of exceptions */
defined( 'EXCEPTION_BASIS' ) or define( 'EXCEPTION_BASIS' ,1000000 );
defined( 'EXCEPTION_INVALID_FIELD' ) or define( 'EXCEPTION_INVALID_FIELD',EXCEPTION_BASIS + 1005 );
We shall extend these definitions later on when it will be time to define more exceptions than the only one we have at this very moment.
static public function strToFile( string $str,string $file )
{
$iRetval = -1;
$str = mb_convert_encoding( $str,'UTF-8' );
if ( $handle = fopen( $file,'w+' ) )
{
$iRetval = fwrite( $handle,$str );
fclose( $handle );
}
return ( $iRetval === mb_strlen( $str ) );
}
static public function fieldLength( $type )
{
static $aTypes = null;
if ( is_null( $aTypes ) )
{
$aTypes['bool' ] =
$aTypes['date' ] = 8;
$aTypes['datetime'] = 14;
$aTypes['float' ] = 16;
$aTypes['int' ] = 8;
/* Indicates that length is manual */
$aTypes['string' ] = -1;
/* Variable length fields */
$aTypes['memo' ] = 4;
} /* if ( is_null( $aTypes ) ) */
return ( $aTypes[$type] ?? -EXCEPTION_INVALID_FIELD /* Indicate an error */ );
}
These two static methods are added to the IllicoDB3 class.
If we now try our test, not only will it not crash, but it will create the DB structure this time. Let's have a look...
Tests of IllicoDB3
Create a DB
The 'firstDB' DB was successfully created
LINE: 76 - Perf: 0,000180 sec to create a new DB
Conclusions
Despite the modest result achieved, we have nevertheless introduced key elements, not the least of which is TDD. We created our first example and we created the first version of our class. We can even consider making what has been developed available to beta users, which I encourage you to do if there is a tangible value to deploy it now.
With the example and the accompanying IllicoDB3 class, we have successfully created a very first DB.
The next article (#3) will aim to determine the tables contained in a DB and physically create the tables in question. We will also be able to determine the fields belonging to a table. For this, we will need two new classes: IllicoDBTable and IllicoDB3Field. We will implement them at a basic level.
At the end of article #3, we will have a DB (firstDB) and a table (firstTable).
We will continue to develop according to TDD principles. We will therefore add examples.
Then, in article #4, we will focus on adding records to the firstTable table and see how to determine the number of records in the table.
See you soon and ... no complications!
Code included in the article (link to https://www.trql.fm/test.illicodb3.code-of-article2.zip) : test.illicodb3.code-of-article2.zip
PS: most of this code is PHP code that was transformed from C code I created in 2002-2004, code that greatly simplified thanks to PHP