Symfony Doctrine ORM pack, Ramsey UUID library, and JMS Serializer - the three tools you need to follow our step-by-step tutorial Show
Co-authored with Zoltan Kincses, Senior Developer at Project A Just over a year ago, we published a tutorial titled “ Serializing data in PHP: A simple primer on the JMS Serializer and FoS Rest”. We heard from a few people who found it helpful, so we decided to publish a follow-up. Both this follow-up and the previous tutorial are aimed at developers who are new to PHP or building APIs in general. We created it to help our own junior developers and we want to share it with you too. This time, we’ll be focusing on interacting with a database rather than just serializing data. You don’t need to have read the previous tutorial to follow this one, but we will be glossing over some steps that are explained in more detail there. Here’s a brief recap of what we did in the previous tutorial:
Serializing data in PHP: A simple primer on the JMS Serializer and FoS RestYou want to create a REST API in PHP? Having a hard time finding decent info about serialization? Check out this super…insights.project-a.com This time around, we’re going to add the following steps.
Here are all the major steps we’ll be covering:
What you’ll need to start You’ll need the same requisites as the previous tutorial, plus a MySQL database server. Here’s a full list of all the prerequisites again:
We’ll also be using some specific PHP bundles but we’ll show you how to install those in the tutorial. It also helps to have a basic knowledge of object-oriented programming in PHP. Ok, let’s go! Setting Up Let’s bootstrap a new Symfony project and install all the extra bundles we need. Create an empty PHP Skeleton ProjectUse Symfony CLI to create a Symfony skeleton project called composer require symfony/orm-pack ramsey/uuid-doctrine 9 (or whatever else you want to call it).Enter the following command: $ symfony new jms-db-tutorial It’s going to create the folder structure and initial files that you need for this tutorial. Install the extra PHP Bundles
composer require jms/serializer-bundle 2. Install, the dependencies that we used in the last tutorial. composer require --dev symfony/maker-bundle 3. Update the FOS Rest config file: types: 0Make sure that it resembles the following example: 4. Install Symfony’s Doctrine ORM pack and the Doctrine-compatible UUID library from Ben Ramsey. composer require symfony/orm-pack ramsey/uuid-doctrine 5. Update the configuration for Doctrine’s Database Abstraction Layer (DBAL) and add UUID as a field type. Open the following file: types: 1In the types: 2 section, add the following lines:types: Your updated configuration file should look like this: Now you’re ready to set up your database. Preparing the Database For the sake of simplicity, we’ll be using the types: 3 user for our database transactions. Of course, in a production scenario, you should never use types: 3. Always create and configure a dedicated database user for your application.However, even when using types: 3, we need to ensure that the root user has a password.Depending on how you installed MySQL, your root user might not have a password initially. For example, if you’re using Codeanywhere, the root user doesn’t have a password by default. So, if this applies to you, first… Set the root user passwordTo give your root user a password, follow these steps:
SET PASSWORD FOR 'root'@'localhost' = PASSWORD('YourNewPassword');
Create a DatabaseWe’re creating an address entity that is usually attached to a customer record of some sort. So let’s call our database ‘customer’. In the MySQL shell, execute the following command: mysql> CREATE DATABASE customer; Configure the Database ConnectionOnce your database is set up, configure your Symfony application to connect to your database. To do configure the database connection, follow these steps:
Your types: 8 should now resemble the following example:Creating the Address Entity If you followed the previous tutorial, you might remember creating an address entity already, but let’s do it again from scratch. This time, we’re going to use the maker bundle which is more efficient. To create the Address entity, follow these steps:
Let’s take a look at what was automatically generated for us. Open the new entity file mysql> CREATE DATABASE customer; 0The first part of the file should resemble the following example: This is a good start, but we need to make a lot of changes to this entity. The first thing we need to do is import the required bundles. Import the UUID and JMS Serializer bundlesUnder the existing imports, add the following lines: use Ramsey\Uuid\Doctrine\UuidGenerator; The first bundle is for UUID generation. We’re using the UUD bundle from Ben Ramsey because it’s well documented and you can also use it independent of Symfony. The second bundle is our trusty JMS serializer to handle the incoming data. As in the last tutorial, we’ll need to add annotations to configure the serializer, but first… A short note about the Repository Class ArgumentBy default, Symfony creates an Entity that’s annotated with a specific Repository class like so: mysql> CREATE DATABASE customer; 1However, in this basic tutorial, we’re not going to use . In a production scenario, the official Symfony documentation encourages you to register repositories this way but it’s also valid to omit this argument (so that the entity is not dependent on a specific repository). You can find a full explanation in the article “How to use Repository with Doctrine as Service in Symfony”. Add the JMS exclusion policy.If you remember from the last tutorial, we want the JMS serializer to ignore all properties except the ones in our Entity. So we exclude everything by default and then expose the permitted properties explicitly. To exclude everything by default, add the annotation mysql> CREATE DATABASE customer; 2underneath the mysql> CREATE DATABASE customer; 3annotation:The first part of your Address class should now look like this: /** Update the ID to be a UUID instead of an integerYou can see that the maker bundle has automatically created an mysql> CREATE DATABASE customer; 4property as well as the properties we explicitly defined in the wizard.You can see that it’s configured to generate simple integers for the entry IDs: /** Every entry in a database needs some kind of ID, but incremental IDs are very easy to guess. We want to make sure that mischievous actors can’t access our database entries by trying out different numbers. Instead, we want to reconfigure the mysql> CREATE DATABASE customer; 4property so that a true UUID is generated for every new database entry (such as ‘ebb5c735–0308–4e3c-9aea-8a270aebfe15’).To do so, update the annotation block with the following changes: composer require jms/serializer-bundle 0What have we done exactly?
Expose properties to override JMS serializer exclusion policyWe need to expose our Address properties for serialization (like we did in the previous tutorial). For each of the remaining Address properties (starting with “name), update the annotation block to include the relevant JMS serializer annotations. Here’s an example of how the annotations should look for the name property: composer require jms/serializer-bundle 1To recap, here’s how your Address entity should look after you’ve completed all the required changes: The entity has been truncated for readability.Creating and Executing the Database Migrations Congratulations! You’re now ready to create a database migration for your address Entity. Migrations allow you to incrementally change your data structures in a way that’s easy to roll back. To create migration for your Entity, run the following command in the root of your project: composer require jms/serializer-bundle 2Assuming everything worked, you’ll find the result of the migration in the use Ramsey\Uuid\Doctrine\UuidGenerator; 1 folder. It will be called something like “Version20211222122815.php”Let’s take a quick look inside: It contains simple SQL commands to run on the customer database. It will create an “address” table and add columns for each of the properties in the entity. Note the columns that have special configurations such as the “Id” column and the “Line3” column. The “Line3” is the only column to have “NULL” as its default — the others are not allowed to be NULL. Assuming everything looks fine, it’s time to execute the migrations on the database. To do so, run the command: composer require jms/serializer-bundle 3The SQL commands are run and the “address” table is created in our “customer” database. To verify this, let’s query our database. Start a MySQL shell with use Ramsey\Uuid\Doctrine\UuidGenerator; 2and run the following commands:composer require jms/serializer-bundle 4You should see a new address table. Now, let’s look at the columns by running composer require jms/serializer-bundle 5You should see that the use Ramsey\Uuid\Doctrine\UuidGenerator; 3column is the primary key and that use Ramsey\Uuid\Doctrine\UuidGenerator; 4is the only column that’s allowed to be NULL:Now it’s time to set up our API endpoint which will write data to this table. Create an Address Controller To create an address controller, we use the same command from the previous tutorial: composer require jms/serializer-bundle 6When prompted, call it “Address”. As before, we get some boilerplate code that we’ll need to change: Add the required importsFirst, import the address entity that we created previously composer require jms/serializer-bundle 7Next, import all the extra bundles that we used in the previous tutorial as well as the Doctrine Entity Manager Interface which is a new addition. composer require jms/serializer-bundle 8Update the Base ClassJust like in the first tutorial, change the base class from use Ramsey\Uuid\Doctrine\UuidGenerator; 5 to use Ramsey\Uuid\Doctrine\UuidGenerator; 6— we want to use the extra features that come with the FOSRest bundle.The class definition should now look like this: composer require jms/serializer-bundle 9Add a property the Entity Manager InterfacesWe want to use the Entity Manager interface to write our data to the database. This type around, we’ll add it using the typed properties that were introduced in PHP 7.4. Inside the use Ramsey\Uuid\Doctrine\UuidGenerator; 7class, assign the Entity Manager interface to a property like so:composer require --dev symfony/maker-bundle 0Underneath the property definition, add the property to a constructor like so: composer require --dev symfony/maker-bundle 1The first part of your controller should now look like this: Create a POST endpoint Now it’s time to define an endpoint to receive our data. Replace the boilerplate use Ramsey\Uuid\Doctrine\UuidGenerator; 8function with an empty use Ramsey\Uuid\Doctrine\UuidGenerator; 9function.composer require --dev symfony/maker-bundle 2Inside the empty function, add the following properties composer require --dev symfony/maker-bundle 3
Next, we add the FOSRest /** 2 method from the previous tutorial:composer require --dev symfony/maker-bundle 4This demonstrates how to serialize the data into another format such as XML. Our POST request will respond with this serialized version of the JSON payload. Your /** 3 action should now look like this:As we’ve noted in the code, you should add validation when using something like this in production. Symfony’s built-in helps you verify data constraints that are defined in an entity class. Test the endpoint OK, now it’s time to see if it works. First off, start the Symfony server by running the following command in the project root: composer require --dev symfony/maker-bundle 5In a new terminal window, enter the following cURL command: composer require --dev symfony/maker-bundle 6Note that the “line3” property has been left empty on purpose. We want to make sure that the database will accept a NULL value in the equivalent column. You should get the same data serialized back as XML. This time, we want to verify that the data was also written to the database. Log into a MySQL shell with use Ramsey\Uuid\Doctrine\UuidGenerator; 2and run the following commands:composer require --dev symfony/maker-bundle 7You should now see that our entry for “Horst Konrader” has made it into the database, complete with UUID. Now we can use that UUID to retrieve the details for the entry from the database. Make a note of the UUD for the next step. Create a GET endpoint Let’s create a new endpoint that accepts a UUID and returns the entry details as XML (with our /** 2method).To create the GET endpoint, follow these steps: 1. Make a copy of the POST function underneath the existing one and remove the existing properties inside it. 2. Change the /** 6annotation as follows:composer require --dev symfony/maker-bundle 83. Remove the /** 7annotation (since we’re not sending a payload, we have nothing to convert).4. Change the function name to /** 8and change the function argument from /** 9to /** 0.This time, we want to work with the UUID which is passed a path segment parameter (rather than the Address payload). 5. Let’s add some error handling in case the UUID doesn’t exist in the database — add the following block inside the function: composer require --dev symfony/maker-bundle 9The /** 1 method is a that returns a 404 status code and an error page when the UUD wasn’t found in the database.6. If the entry does exist, we want to serialize it into XML and return it as a response — to do so, add the following line. composer require --dev symfony/maker-bundle 4The full /** 8action should look like this:Now let’s try it out. Send a GET request with the following command but replace the UUID with the one you noted from the previous section. composer require symfony/orm-pack ramsey/uuid-doctrine 1You should get the same XML details back as your response. composer require symfony/orm-pack ramsey/uuid-doctrine 2Create a DELETE endpoint Finally, let’s remove the entry with a DELETE endpoint. To create the DELETE endpoint, follow these steps:
2. Change the /** 6annotation as follows:composer require symfony/orm-pack ramsey/uuid-doctrine 33. Change function name to /** 4.We still need the same error handling that we had for the GET request, so keep that section. 4. Underneath the error handling block, add the following lines: composer require symfony/orm-pack ramsey/uuid-doctrine 4
5. Lastly, we want to return a simple status code if the operation completed successfully — to do so, add the following line: composer require symfony/orm-pack ramsey/uuid-doctrine 5Your /** 4 action should now look like this:OK, you know the drill. Let’s test it by running a request with the following command: composer require symfony/orm-pack ramsey/uuid-doctrine 6To make sure that it worked, let’s query our database again. Start a MySQL shell with use Ramsey\Uuid\Doctrine\UuidGenerator; 2and run the following commands:composer require symfony/orm-pack ramsey/uuid-doctrine 7The entry with the noted UUID should hopefully be gone. Wrapping Up Congratulations for making it this far! If you want to see the entire Address Entity and Controller, take a look at the gists linked below: To see what else you can do with databases (such as updating or listing entries), check out the Symfony documentation on Databases and the Doctrine ORM. In tutorials like these, we try to fill in any documentation gaps that we found to be lacking on our own journeys to PHP mastery. And we hope this simple primer helped to bring you a little bit closer to creating a full-fledged PHP application yourself. If you feel like you’re ready to build this example out even further, try mastering and implementing JWT Authentication. And as always, if there’s anything else you think isn’t documented very well and could use a decent write-up, let us know! |