Creating a Web App with Symfony 4: Entity Services

This is a continuation from the previous post. If you haven’t read it yet, click here!

Now that we have our entities, we need to make some services that can handle managing them, similar to the UserService we have already. Since these entity services are going to mostly be doing the same thing (creating, validating and saving), we’re going to make a base class that handles that functionality. Then we’ll extend the base class for the individual entity services so there isn’t a ton of code duplication. So let’s get started!

In the src/Service/Entity folder, create a new file called BaseEntityService.php. Set it up as an empty abstract class. We’re making it abstract because we shouldn’t be using this base class on it’s own. It should only ever be used for child classes that extend it.

Then let’s think for a second about what we’d need to create, validate and save an entity. We’d need to know the type of entity that we’re working with. So let’s add a protected property called $entityClass. It’s been a while since we last saved an entity, but if you remember from the UserService class, we needed an ObjectManager, and we used the persist and flush methods to save an entity to the database. So we’ll need a property that holds an ObjectManager too. Add a protected property called $om to your class. In order to validate an entity, we need to use our ValidationService, so let’s add a protected property for $validator. We will also need an array to store any errors, so create a protected $errors property. Lastly, we need a property to hold the actual entity that we’re working on, not just the type. So create a protected property named $entity. And we’re using protected properties here so child classes can access them.

Next let’s set up the constructor. We will need to use dependency injection to get the ObjectMangager and ValidationService. You will need to add use statements at the top of your file to import those classes:

use Doctrine\Common\Persistence\ObjectManager;
use App\Service\ValidationService;

Then use the constructor parameters to assign the $om and $validator properties. For the $entityClass property, we’re going to set that in the individual entity service classes. You’ll see how we do that after we finish this base class. But, we should prevent using this base class without having an entity class defined. So if there is no entity class, throw an exception.

With all of that said, try to build out this portion of the class on your own. Add the properties and constructor. When your done or need some help, take a look at my code below:

<?php
namespace App\Service\Entity;

use Doctrine\Common\Persistence\ObjectManager;
use App\Service\ValidationService;



abstract class BaseEntityService
{
   protected $entityClass;
   protected $om;
   protected $validator;
   protected $errors = [];
   protected $entity;


   public function __construct(ObjectManager $om, ValidationService $validator)
   {
       $this->om = $om;
       $this->validator = $validator;

       if(empty($this->entityClass))
       {
           throw new \Exception("Missing entity class.");
       }
   }
}

For this base class, I’m going to separate out the creation of an entity and saving/validating of an entity into separate methods. I’m doing it that way because it would allow for you to update existing entities using the save method. We wouldn’t need to go through the create method if we were just updating.

Let’s start with the create method. Similar to the one in our UserService, lets have it take in an array of properties. The first thing we can do is create a new instance of the entity using $this->entityClass:

public function create($properties = [])
{
   $this->entity = new $this->entityClass();
}

Next we need to assign the properties. How will we do that? Well, the only way to assign a value to an entity’s property is using it’s setter method (unless we’re working inside the entity itself). So if we loop over the properties and check if there is a set[PropertyName] method, we know we can set it. Try to write that loop code out on your own. If you need a hint, there is a PHP function called method_exists which takes an object as it’s first parameter and the name of the method as the second. With method names not being case sensitive you don’t need to worry about if the property is upper or lower case. If you’re done or still not sure here’s the code:

public function create($properties = [])
{
   $this->entity = new $this->entityClass();
   foreach($properties as $property => $value)
   {
	   $setterMethod = "set".$property;
	   if(method_exists($this->entity, $setterMethod))
	   {
		   $this->entity->$setterMethod($value);
	   }
   }
}

If you were able to write that on your own, great! If not, don’t worry about it. The more you work with Symfony and PHP in general you will get a better handle on what you can do with it. One other thing we should do with the create method is make sure that we are only creating new entities. Meaning, we should not be setting the id from this create method. So in the foreach loop, check to make sure $property does not equal id and if it does, skip the iteration. Here’s the updated create method code:

public function create($properties = [])
{
   $this->entity = new $this->entityClass();
   foreach($properties as $property => $value)
   {
	   if(strtolower($property) === "id")
	   {
		   continue;
	   }

	   $setterMethod = "set".$property;
	   if(method_exists($this->entity, $setterMethod))
	   {
		   $this->entity->$setterMethod($value);
	   }
   }

}

We’re going to pause construction on the create method for now and write the save method. The save method will handle validating and then saving. It will save the entity that’s assigned to $this->entity. So we should first check to make sure that $this->entity isn’t empty before continuing anything. Then validate, if all is good persist and flush. If there are errors, assign it to $this->errors. Try writing that code out yourself. If you need a hint, take a look at how the validating and saving is done in the UserService. It’s going to be pretty similar. Here is our save method:

public function save()
{
   if(!empty($this->entity))
   {
	   $isValid = !$this->errors && $this->validator->validate($this->entity);
	   if($isValid)
	   {
		   // Save entity
		   $this->om->persist($this->entity);
		   $this->om->flush();

		   // Reset the errors array after saving is successful
		   $this->errors = [];

		   return true;
	   }
	   else
	   {
		   $this->errors = array_merge($this->errors, $this->validator->getErrors());
	   }
   }
   else
   {
	   $this->errors[] = "The entity being saved was empty.";
   }

   return false;
}

There’s two extra things I added that I didn’t mention above. The first being that I am checking to make sure $this->errors doesn’t having anything in it along with calling that validate method on the validator. I’m doing that so we can prevent an entity from being saved by adding an error to the errors array. It won’t be used in this base class, but for any child classes that inherit this one, we can do some extra validation (like we have to do in the UserService class) outside of our ValidationService and append them to the errors array to prevent the save from going through. If you also notice, I’m using array_merge to merge $this->errors and the errors from the validator. The second thing I added was resetting the errors array after saving is successful. That’s just to prevent any old errors from sticking around when they aren’t relevant.

Now let’s go back to the create method. After the foreach loop, have it return $this. That will allow us to chain the create and save methods together so to create a new entity you would do something like:

$service->create($properties)->save();

I decided to have the create method only create and not actually save to the database so we can intercept the entity before it actually saves. This will be useful to do for some entities that require a little extra work to save. You will an example of this when we work on converting the UserService class to use this base class.

There are few getter and setter methods that will be helpful on this base class. One important one being getErrors, to get any errors. We should also have a setEntity method that we can use when we’re updating an existing entity. And we’ll need a getEntity method to get any new entities that we create. For the setEntity method, we should also check to make sure that the entity being set is the same class as our $entityClass property. PHP has a method called is_a, which takes an object as the first parameter and a class name as the second. You can use that to check that the class is correct. If it’s not correct, throw an exception. Try to write that setter and the 2 getters on your own. Here’s how I did it:

public function getErrors()
{
   return $this->errors;
}

public function setEntity($entity)
{
   if(!is_a($entity, $this->entityClass))
   {
	   throw new \Exception("Setting invalid entity.  Expecting entity to be of type: ".$this->entityClass);
   }

   $this->entity = $entity;
}

public function getEntity()
{
   return $this->entity;
}

One other thing we should include is a way to set properties similar to the create method, except use the existing entity rather than creating a new one. Let’s create a new method called setProperties that takes in a $properties array as a parameter. We’ll copy the create method’s code since it will mostly be the same:

public function setProperties($properties = [])
{
   $this->entity = new $this->entityClass();
   foreach($properties as $property => $value)
   {
	   if(strtolower($property) === "id")
	   {
		   continue;
	   }

	   $setterMethod = "set".$property;
	   if(method_exists($this->entity, $setterMethod))
	   {
		   $this->entity->$setterMethod($value);
	   }
   }

   return $this;
}

Now, we can remove the first line since we aren’t making a new entity. But, we should wrap the foreach loop in an if statement to check that there is an entity set. We can use the getEntity method for that. Then also inside the foreach loop, any reference to $this->entity, lets change to $this->getEntity() for consistency. If you can try to make those changes on your own and then take a look at my code below:

public function setProperties($properties = [])
{
   if($this->getEntity())
   {
	   foreach($properties as $property => $value)
	   {
		   if(strtolower($property) === "id")
		   {
			   continue;
		   }

		   $setterMethod = "set".$property;
		   if(method_exists($this->getEntity(), $setterMethod))
		   {
			   $this->getEntity()->$setterMethod($value);
		   }
	   }
   }

   return $this;
}

We still want to prevent setting the id since we wouldn’t want to change the id of an entity whether we were creating or updating. Also, if we wanted to, we could expand this a little more to prevent other fields from being set through this method, but for simplicity sake, we’ll leave it at just id.

With the setProperties method created, update the create method to use that along with the setEntity method we added:

public function create($properties = [])
{
   $this->setEntity(new $this->entityClass());
   return $this->setProperties($properties);
}

Here is what your completed BaseEntityService class should look like:

<?php
namespace App\Service\Entity;

use Doctrine\Common\Persistence\ObjectManager;
use App\Service\ValidationService;



abstract class BaseEntityService
{
   protected $entityClass;
   protected $om;
   protected $validator;
   protected $errors = [];
   protected $entity;

   public function __construct(ObjectManager $om, ValidationService $validator)
   {
       $this->om = $om;
       $this->validator = $validator;

       if(empty($this->entityClass))
       {
           throw new \Exception("Missing entity class.");
       }
   }

   public function create($properties = [])
   {
       $this->setEntity(new $this->entityClass());
       return $this->setProperties($properties);
   }

   public function setProperties($properties = [])
   {
       if($this->getEntity())
       {
           foreach($properties as $property => $value)
           {
               if(strtolower($property) === "id")
               {
                   continue;
               }

               $setterMethod = "set".$property;
               if(method_exists($this->getEntity(), $setterMethod))
               {
                   $this->getEntity()->$setterMethod($value);
               }
           }
       }

       return $this;
   }

   public function save()
   {
       if(!empty($this->entity))
       {
           $isValid = !$this->errors && $this->validator->validate($this->entity);
           if($isValid)
           {
               // Save entity
               $this->om->persist($this->entity);
               $this->om->flush();

               // Reset the errors array after saving is successful
               $this->errors = [];

               return true;
           }
           else
           {
               $this->errors = array_merge($this->errors, $this->validator->getErrors());
           }
       }
       else
       {
           $this->errors[] = "The entity being saved was empty.";
       }

       return false;
   }

   public function getErrors()
   {
       return $this->errors;
   }

   public function setEntity($entity)
   {
       if(!is_a($entity, $this->entityClass))
       {
           throw new \Exception("Setting invalid entity.  Expecting entity to be of type: ".$this->entityClass);
       }

       $this->entity = $entity;
   }

   public function getEntity()
   {
       return $this->entity;
   }
}

With that, we have our base class that we can use for all of our other entity services. I’ll show you how you can use this with one of the entities we just made, and then I’ll go over converting our current UserService to use this base class as well.

Create a new file in the src/Service/Entity folder called AddressService.php. Set it up as an empty class that extends the BaseEntityService class we made. Now, the only thing we need to do is tell the class which entity this service is working with. To do that, we need to intiialize our $entityClass property with the Address entity class. You can do this by setting $entityClass equal to Address::class. Try coding out the class on your own and if you need help, take a look at my code:

<?php
namespace App\Service\Entity;

use App\Entity\Address;

class AddressService extends BaseEntityService
{
   protected $entityClass = Address::class;
}

And, that’s all we need to do! For the simpler entities, this is how our entity service classes are going to look. A basic class with just the $entityClass property defined. Do the same thing for the other new entity classes we’ve made so far. When you’re finished, we will work on updating our UserService to use the BaseEntityService class.


Open the UserService.php file in src/Service/Entity. The first thing we’ll do is have it extend BaseServiceEntity. Then we can remove the $validator, $om, $errors and $user properties from the class since those are created in the base service class. We need to add an $entityClass property like we did in the other entity service classes. Set it equal to User::class.

Next, we’ll need to modify the constructor a little. One quick thing is to swap the $validator and $om parameters. It’s not completely necessary, but just to remain consistent with the base class, have the $om parameter be first and the $validator be second.

Then in the constructor remove all of the assignments except for the $passwordEncoder one. We still need that. Have your constructor call the parent constructor and pass in the $om and $validator parameters so they get set. See if you can set that part up on your own and then take a look at my code below. This is what the top portion of your UserService class should look like:

<?php
namespace App\Service\Entity;

use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use App\Service\ValidationService;
use App\Entity\User;

class UserService extends BaseEntityService
{
   protected $passwordEncoder;
   protected $entityClass = User::class;



   public function __construct(ObjectManager $om, ValidationService $validator, UserPasswordEncoderInterface $passwordEncoder)
   {
       parent::__construct($om, $validator);
       $this->passwordEncoder = $passwordEncoder;
   }

Next we need to modify the create method. We should call the parent create method first and pass in the properties parameter. This will create a new user entity for us and initialize any of the values in $properties, but it will not save it.

We can remove the $email variable since that won’t be used anymore. The password confirmation and password length checks can stay but the rest we’ll need to modify a little. In the if(!$errors) block, we can delete everything except for the $encodedPassword line, since most of what gets done there was done in the parent create method. We also are not saving to the database in our create method anymore, that gets done in the save method.

The important thing we need to do in this if block is to encode the password. Right now, the user entity has an unencoded password assigned to it’s password field. Currently the $encodedPassword variable that we left is using the old $user entity variable. Change that to $this->getEntity() to use the entity that was made in the parent create method.

Then we need to assign the encoded password. Using $this->getEntity(), call setPassword and assign it the encoded password. That if(!$errors) block should now look like this:

if(!$errors)
{
   $encodedPassword = $this->passwordEncoder->encodePassword($this->getEntity(), $password);
   $this->getEntity()->setPassword($encodedPassword);
}

The last thing we need to change is the last line that returns false. Change that to return $this. Your create method should now look like this:

public function create($properties = [])
{
   parent::create($properties);
   $password             = isset($properties['password']) ? $properties['password'] : "";
   $passwordConfirmation = isset($properties['password_confirmation']) ? $properties['password_confirmation'] : "";

   $errors = [];
   if($password != $passwordConfirmation)
   {
	   $errors[] = "Password does not match the password confirmation.";
   }

   if(strlen($password) < 6)
   {
	   $errors[] = "Password should be at least 6 characters.";
   }

   if(!$errors)
   {
	   $encodedPassword = $this->passwordEncoder->encodePassword($this->getEntity(), $password);
	   $this->getEntity()->setPassword($encodedPassword);
   }

   $this->errors = $errors;

   return $this;
}

Next, we can delete the getUser and getErrors methods. getUser is now getEntity and getErrors is already created in our base class.

One final thing we need to do is update our UserController. So open the src/ControllerUserController.php file. In the register function, after $userService calls create, chain a call to the save method onto the end. Then, change the call to getUser to getEntity.

Try making those changes on your own and then take a look at my code below:

public function register(Request $request, UserService $userService)
{

   if($userService->create($request->request->all())->save())
   {
	   return $this->json([
		   'user' => $userService->getEntity()
	   ]);
   }


   return $this->json([
	   'errors' => $userService->getErrors()
   ], 400);

}

Now let’s make sure that our user registration is still working. Open Postman and setup a request to register a new user.

Here’s what it should look like (your site url may be different):

Click send and it should create a new user if you don’t already have a user with that email. Test it out so that it creates an error as well. Try a short password, try a different password and password_confirmation. It should all still be working as it was before.

Great! We’re done with our entity services. Next time we’ll work on creating our API endpoints. As always, if you have any questions or feedback feel free to leave a comment and if you want to be notified when the next post is out, sign up for the newsletter below!