Documentation

Preparing for different Database-Backends — Zend Framework 2 2.4.9 documentation

In-depth tutorial for beginners

Preparing for different Database-Backends

In the previous chapter we have created a PostService that returns some data from blog posts. While this served an easy to understand learning purpose it is quite impractical for real world applications. No one would want to modify the source files each time a new post is added. But luckily we all know about databases. All we need to learn is how to interact with databases from our ZF2 application.

But there is a catch. There are many database backend systems, namely SQL and NoSQL databases. While in a real-world you would probably jump right to the solution that fits you the most at the time being, it is a better practice to create another layer in front of the actual database access that abstracts the database interaction. We call this the Mapper-Layer.

What is database abstraction?

The term “database abstraction” may sound quite confusing but this is actually a very simple thing. Consider a SQL and a NoSQL database. Both have methods for CRUD (Create, Read, Update, Delete) operations. For example to query the database against a given row in MySQL you’d do a mysqli_query('SELECT foo FROM bar'). But using an ORM for MongoDB for example you’d do something like $mongoODM->getRepository('bar')->find('foo'). Both engines would give you the same result but the execution is different.

So if we start using a SQL database and write those codes directly into our PostService and a year later we decide to switch to a NoSQL database, we would literally have to delete all previously coded lines and write new ones. And in a few years later a new thing pops up and we have to delete and re-write codes again. This isn’t really the best approach and that’s precisely where database abstraction or the Mapper-Layer comes in handy.

Basically what we do is to create a new Interface. This interface then defines how our database interaction should function but the actual implementation is left out. But let’s stop the theory and go over to code this thing.

Creating the PostMapperInterface

Let’s first think a bit about what possible database interactions we can think of. We need to be able to:

  • find a single blog post
  • find all blog posts
  • insert new blog post
  • update existing blog posts
  • delete existing blog posts

Those are the most important ones I’d guess for now. Considering insert() and update() both write into the database it’d be nice to have just a single save()-function that calls the proper function internally.

Start by creating a new file inside a new namespace Blog\Mapper called PostMapperInterface.php and add the following content to it.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
 <?php
 // Filename: /module/Blog/src/Blog/Mapper/PostMapperInterface.php
 namespace Blog\Mapper;

 use Blog\Model\PostInterface;

 interface PostMapperInterface
 {
     /**
      * @param int|string $id
      * @return PostInterface
      * @throws \InvalidArgumentException
      */
     public function find($id);

     /**
      * @return array|PostInterface[]
      */
     public function findAll();
 }

As you can see we define two different functions. We say that a mapper-implementation is supposed to have one find()-function that returns a single object implementing the PostInterface. Then we want to have one function called findAll() that returns an array of objects implementing the PostInterface. Definitions for a possible save() or delete() functionality will not be added to the interface yet since we’ll only be looking at the read-only side of things for now. They will be added at a later point though!

Refactoring the PostService

Now that we have defined how our mapper should act we can make use of it inside our PostService. To start off the refactoring process let’s empty our class and delete all current content. Then implement the functions defined by the PostServiceInterface and you should have an empty PostService that looks like this:

The first thing we need to keep in mind is that this interface isn’t implemented in our PostService but is rather used as a dependency. A required dependency, therefore we need to create a __construct() that takes any implementation of this interface as a parameter. Also you should create a protected variable to store the parameter into.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
 <?php
 // Filename: /module/Blog/src/Blog/Service/PostService.php
 namespace Blog\Service;

 use Blog\Mapper\PostMapperInterface;

 class PostService implements PostServiceInterface
 {
     /**
      * @var \Blog\Mapper\PostMapperInterface
      */
     protected $postMapper;

     /**
      * @param PostMapperInterface $postMapper
      */
     public function __construct(PostMapperInterface $postMapper)
     {
         $this->postMapper = $postMapper;
     }

     /**
      * {@inheritDoc}
      */
     public function findAllPosts()
     {
     }

     /**
      * {@inheritDoc}
      */
     public function findPost($id)
     {
     }
 }

With this we now require an implementation of the PostMapperInterface for our PostService to function. Since none exists yet we can not get our application to work and we’ll be seeing the following PHP error:

1
2
3
4
 Catchable fatal error: Argument 1 passed to Blog\Service\PostService::__construct()
 must implement interface Blog\Mapper\PostMapperInterface, none given,
 called in {path}\module\Blog\src\Blog\Service\PostServiceFactory.php on line 19
 and defined in {path}\module\Blog\src\Blog\Service\PostService.php on line 17

But the power of what we’re doing lies within assumptions that we can make. This PostService will always have a mapper passed as an argument. So in our find*()-functions we can assume that it is there. Recall that the PostMapperInterface defines a find($id) and a findAll() function. Let’s use those within our Service-functions:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
 <?php
 // Filename: /module/Blog/src/Blog/Service/PostService.php
 namespace Blog\Service;

 use Blog\Mapper\PostMapperInterface;

 class PostService implements PostServiceInterface
 {
     /**
      * @var \Blog\Mapper\PostMapperInterface
      */
     protected $postMapper;

     /**
      * @param PostMapperInterface $postMapper
      */
     public function __construct(PostMapperInterface $postMapper)
     {
         $this->postMapper = $postMapper;
     }

     /**
      * {@inheritDoc}
      */
     public function findAllPosts()
     {
         return $this->postMapper->findAll();
     }

     /**
      * {@inheritDoc}
      */
     public function findPost($id)
     {
         return $this->postMapper->find($id);
     }
 }

Looking at this code you’ll see that we use the postMapper to get access to the data we want. How this is happening isn’t the business of the PostService anymore. But the PostService does know what data it will receive and that’s the only important thing.

The PostService has a dependency

Now that we have introduced the PostMapperInterface as a dependency for the PostService we are no longer able to define this service as an invokable because it has a dependency. So we need to create a factory for the service. Do this by creating a factory the same way we have done for the ListController. First change the configuration from an invokables-entry to a factories-entry and assign the proper factory class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 <?php
 // Filename: /module/Blog/config/module.config.php
 return array(
     'service_manager' => array(
         'factories' => array(
             'Blog\Service\PostServiceInterface' => 'Blog\Factory\PostServiceFactory'
         )
     ),
     'view_manager' => array( /** ViewManager Config */ ),
     'controllers'  => array( /** ControllerManager Config */ ),
     'router'       => array( /** Router Config */ )
 );

Going by the above configuration we now need to create the class Blog\Factory\PostServiceFactory so let’s go ahead and create it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 <?php
 // Filename: /module/Blog/src/Blog/Factory/PostServiceFactory.php
 namespace Blog\Factory;

 use Blog\Service\PostService;
 use Zend\ServiceManager\FactoryInterface;
 use Zend\ServiceManager\ServiceLocatorInterface;

 class PostServiceFactory implements FactoryInterface
 {
     /**
      * Create service
      *
      * @param ServiceLocatorInterface $serviceLocator
      * @return mixed
      */
     public function createService(ServiceLocatorInterface $serviceLocator)
     {
         return new PostService(
             $serviceLocator->get('Blog\Mapper\PostMapperInterface')
         );
     }
 }

With this in place you should now be able to see the ServiceNotFoundException, thrown by the ServiceManager, saying that the requested service cannot be found.

1
2
3
4
5
6
 Additional information:
 Zend\ServiceManager\Exception\ServiceNotFoundException
 File:
 {libraryPath}\Zend\ServiceManager\ServiceManager.php:529
 Message:
 Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance for Blog\Mapper\PostMapperInterface

Conclusion

We finalize this chapter with the fact that we successfully managed to keep the database-logic outside of our service. Now we are able to implement different database solution depending on our need and change them easily when the time requires it.

In the next chapter we will create the actual implementation of our PostMapperInterface using Zend\Db\Sql.

Copyright

© 2006-2018 by Zend, a Rogue Wave Company. Made with by awesome contributors.

This website is built using zend-expressive and it runs on PHP 7.

Contacts