Okay, guys.
So last time we've created this dependency injection container, we've also moved some startup code
to the bootstrap file and the config of the app into the config file.
Our next step is to create a database abstraction so that we can first initialize the database using
the command line script, and then just work with the database inside the app.
So that's another thing we are adding to our core namespace.
Let me call this database.
And that would be a class that we're going to use to work with the database.
So that's our database class.
And inside it we'd like to have the PDO instance.
This is what we create when this class is created.
That means we next up need to add a constructor.
And this will accept the config by the way the config is an array.
It's exactly what we have under the database.
Key inside our config.php file.
Now our goal is to make this config as universal as possible.
And we are using PDO here.
PDO is a database abstraction in PHP that can work with a lot of different databases.
So this constructor job is to connect to the database and just throw an exception if it fails.
So we should be adding a try catch block inside.
And the exception kind I'd like to catch is this PDO exception.
So when it happens I'd like to throw our own custom exception.
That would just say could not connect to the database.
Maybe without any more details.
Okay, now the connection itself.
So we've got this PDF field we need to initialize it by creating a new PDL object.
As you see, it accepts different parameters DSN, which stands for Data Source name.
I'm going to go back to it in a second and then optionally the database user username, then the password,
and then some additional options if you have them okay.
So first the DSN.
Well that's just a string that you construct which can contain info like the port, the database name,
etc..
This can look a little bit different for different databases.
That's why we should add a method that will generate a proper DSN depending on the driver.
So I can call that create DSN.
And it's going to accept our config array and return a string.
So if we take a look at the config again we've got this driver setting and this database entry inside
the config should accept the properties that are specific to our database.
So in case of SQLite, we only want the path if we would be using MySQL or Postgres, this would also
be username, password and the database name etc..
So here let's get the driver from the config driver.
So the config that we are receiving here is this database key.
So we are not getting the whole config.
Next up let's get the database name.
So we've got this configured differently in the config dot path.
So it's just because in SQLite the database name is actually the path to the file.
But to make it more universal let's call it database name.
So we would.
So then it will also make more sense with other database systems.
And now we should return the value from matching the driver, as there will be different DSN depending
on the driver.
So we only support SQLite.
You can add more later on if you prefer.
And in SQLite it looks like this starts with SQLite colon followed by the file name which is just database
name in our case.
Let's also add the default case, which would just throw a new exception because we've got an unsupported
driver.
So let's say unsupported driver or maybe be specific database driver.
Driver.
In case someone misspells the database driver it will also catch that and throw an exception.
So this is our DSN function.
So going back to the constructor, which will construct the database, we need to grab the DSN.
So we call this create DSN passing the config.
So we've got this.
We can pass that to PDL.
That's the only required argument.
But then we also should support the username.
So we get that from the config.
Only if it's present.
So we do config username.
But if it's not set we default to null.
Now let's do the same with the password.
So only if the password is present in the options we use it.
And finally the third thing is options.
We use the options from the config if they are set.
If not we ignore them.
And now let's pass that username password.
And the fourth argument is options.
Now we have a pretty universal config that will connect to different kinds of databases, but obviously
we need to add those dsns for different databases.
I might add that at the end of this video.
So you can see how this would look like.
For now, we need to add some useful methods that will make using the database easier.
Let's start with a query method.
Now let me explain.
Why do we even create this abstraction?
Because that's not obvious and you are probably guessing why.
But let me be clear about that.
So we just want one common way of connecting to database and interacting with the database throughout
the whole application and in the future, the next projects.
That's why we are building this small framework, let's call it this way.
And we also want to be really strong on security.
So what I mean by that is there are some best practices when it comes to working with databases, like
using prepared statements and using PDO, not some other functions that are also available in PHP.
That's why we create this database class so we have best practices in it.
It's universal.
It can work with different database systems and also it will just save us some work.
Its job is to always connect the same way, use some sensible default config and avoid any security
vulnerabilities.
So speaking about the vulnerabilities, let's add the first public function called query.
So we need to accept the SQL and the optional params of type array that's initialized with an empty
array.
This will just return the PDO statement.
So we will use prepared statements.
And this will protect us from vulnerability called SQL injection so that we make sure no one will pass
any additional SQL through parameters that can be dangerous to our database and our users.
That's why we call PDO prepare passing the SQL given.
Then on the statement object that we've created, we execute it.
So this PDO statement has the execute methods only.
Then we pass the parameters and then we return the statement so something else can be done with it.
Okay.
next up we are going to use it twice ourselves.
So let's add another useful method to this class.
This will be called Fetch all.
That's something that we're gonna be using probably quite often.
So we are passing the SQL and then params which we initialize with an empty array.
And this should return an array of things.
And we're going to use this query.
And there we pass our SQL with params.
But then it returns this PDF statement that we can only then do something with.
And we do fetch.
All this would just fetch all the results and we want to use fetch um object.
Let me find this constant so that every record would be fetched as an object.
We're going to have some model classes later on, and every model will just represent specific table
and the results from that table.
That's why I want to use objects.
Okay.
So this would be pretty useful.
And another pretty useful thing is just the fetch method.
This will be used to fetch one record at a time.
And it has the same signature as fetch all except it doesn't return an array.
It returns an object.
So let's use this type and object are false because we've got two possible types here.
That's why I'm using the so-called union type.
You need to go back to PHP basics.
If you don't know what that is.
And then I'm calling query.
Same way with the SQL and the params.
And then I follow with fetch.
So I'd like to use fetch object again.
Okay, we're almost done here.
So the last method is last insert id.
So obviously we're not going to be only fetching the data.
We're also going to be inserting it.
And this will return a string or false.
So again union type.
And this is just on PDF.
We're going to call last insert ID.
Now as you see last insert ID will return either string or false itself.
So this would be useful to grab the ID of just inserted record.
And this class is now complete.
Well it is almost complete.
I forgot about one pretty important thing which is to set some attributes of the PDL.
And this is the PDL error mode.
So this is attribute error mode.
And I'd like all the errors that happen during whatever we do with databases to be the.
Can I find this on the list error mode exception?
Yes, this is what I meant.
I'd like the exception to be thrown every single time there is any kind of issue with the databases,
so that we can catch those in the first place.
Okay, now we are done.
At least we are done with implementing our tiny database abstraction, which would save us a lot of
time.
I think, again, we should take a break right now and only then next up we're gonna wire it together
with the config, create a database object that we can actually use.
And the first use case would be to create a command line interface command that would create our SQLite
database tables.