Skip to main content

Bootstrapping ZF1 application in Apigilty

Apigility
Apigility is a Zend Framework 2 tool that provides a REST API management interface, which is very useful if you want to build an API.
Apigility can directly connect with your database and offer a full REST API for your application, but in most cases you already have an application build with Zend Framework 1.x (ZF1). Let's assume you have incorporated a lot of business logic in this application so it would be a waste not to use it building a rich REST API.
This article describes what needs to be done to incorporate your ZF1 application into a vanilla installation of Apigility. It will not describe the installation of Apigility as this is fully documented on the Apigility website.
NOTE: We use our demo ZF1 application as example.

Including your ZF1 app into Apigility

The easiest way is to use Gitmodules if you have your ZF1 project in GIT. If you have your ZF1 project in SVN, you need to do a manual checkout of the project.
Since Apigility uses Composer for installation and updates, we decide that our ZF1 application should be installed in the vendordirectory.

Using Gitmodules

As said, the easiest way would be to use Gitmodules.
$ git submodule add https://github.com/in2it/zfdemo.git vendor/zfdemo

Using Subversion

Alternatively we can use Subversion
$ svn co https://github.com/in2it/zfdemo/trunk vendor/zfdemo
NOTE: This requires often manual updates if the source of your ZF1 changes!

Zend Framework 1

If you're application does not come pre-installed with Zend Framework, the easiest way to include it in your project is to add it to your composer.json of Apigility.
Add the following to your Apigility composer.json:
"zendframework/zendframework1": "1.12.5"
This should be added within the require segment, as displayed below.
"require": {
    ...
    "zendframework/zendframework1": "1.12.5"
}
Now add a symlink in your Zend Framework 1 library pointing to this repository.
$ cd vendor/zfdemo/library
$ ln -s ../../zendframework/zendframework1/library/Zend Zend
$ cd ../../.. (application root)
With this setup, you now have the ZF1 library autoloaded

Custom libraries

If your application uses 3rd-party libraries or custom libraries, you need to see if they exists as Composer packages or if they are available through SCM (GIT or Subversion).
For our zfdemo, we depend on In2it library which is on GitHub, but not available as a Composer package. So for our own convenience we add them as a Gitmodule.
We need to make the exception in our .gitignore file to allow adding our library in, so we add the following line into our .gitignore file.
!vendor/In2it
Now we can safely add the library as Gitmodule.
 $ git submodule add https://github.com/in2it/In2it.git vendor/In2it
Lastly we add a symlink in our vendor/zfdemo/library to point to our In2it library.
$ cd vendor/zfdemo/library
$ ln -s ../../In2it/library/In2it In2it
$ cd ../../.. (application root)

Changing APPLICATION_PATH in index

The entry point for Apigility's web interface, sets up the include paths, environments and loads the ZF2 application. But it also uses constants that are used in ZF1 which conflict when using both applications at the same time.
We therefor change the Apigility public/index.php file and replace APPLICATION_PATH into ZF2APP_PATH. So the public/index.php file looks like this:
<?php
/**
 * @license   http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
 * @copyright Copyright (c) 2014 Zend Technologies USA Inc. (http://www.zend.com)
 */

/**
 * This makes our life easier when dealing with paths. Everything is relative
 * to the application root now.
 */
chdir(dirname(__DIR__));

// Decline static file requests back to the PHP built-in webserver
if (php_sapi_name() === 'cli-server' && is_file(__DIR__ . parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH))) {
    return false;
}

if (!file_exists('vendor/autoload.php')) {
    throw new RuntimeException(
        'Unable to load ZF2. Run `php composer.phar install` or define a ZF2_PATH environment variable.'
    );
}

// Setup autoloading
include 'vendor/autoload.php';

if (!defined('ZF2APP_PATH')) {
    define('ZF2APP_PATH', realpath(__DIR__ . '/../'));
}

$appConfig = include ZF2APP_PATH . '/config/application.config.php';

if (file_exists(ZF2APP_PATH . '/config/development.config.php')) {
    $appConfig = Zend\Stdlib\ArrayUtils::merge($appConfig, include ZF2APP_PATH . '/config/development.config.php');
}

// Run the application!
Zend\Mvc\Application::init($appConfig)->run();

Autoloading of ZF1 classes and services

Now the most challenging part of this assignment is to autoload our ZF1 classes and services, we mighte even need to make the ZF1 classes available (but we can always add them as a seperate library in vendor directory).
I had a discussion with the Aleksey (@xerkus) and Evan (@EvanDotPro) of Roave regarding the best way to bootstrap ZF1 applications in ZF2 architectures.
It seemed that Aleksey already created a ZF2for1 bootstrapper, where they allowed ZF2 resources to become available in ZF1 applications. A reverse way of what we want to achieve.
I created a zf1to2 bootstrapper in my zfdemo application, which is basically the same as a vanilla ZF1 public/index.php file, except it doesn't contain a run() call on $application->bootstrap(). It just needs to bootstrap the application without running it.

Getting started with Apigility

Apigility requires read/write permissions on the configuration files, so don't forget to allow write access for your application if you're using a web server (like Apache or Nginx). If you run Apigility from the buildin PHP server, you won't have any issues as the user you run the app is most likely the same that owns the configuration files.
$ chmod -R go+w config/ data/ module/
When you go the url of your Apigility project (in my case it's http://zf-apigility.local), you should see the welcome screen.
Apigility Welcome
Now it's time to add endpoints. So in "Admin" -> "API's" we create an new API for "zfdemo", our demo application. Of course you can replace this with your own application name.
New zfdemo API
Now we add REST endpoints to our application. To start we define an endpoint for "user" and we choose a "Code-Connect" endpoint.
REST user endpoint
When we look at the "resources" tab, we see three defined files there:
  • Collection Class: zfdemo\V1\Rest\User\UserCollection.php
  • Entity Class: zfdemo\V1\Rest\User\UserEntity.php
  • Resource Class: zfdemo\V1\Rest\User\UserResource.php
We only need to work with the Resource Class file as this is the one actually building the logic in a similar way we build it initially in our demo application.
To test our installation, we can make a GET call to http://zf-apigilty.local/v1/user and we should receive the following JSON string
{"type":"http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html","title":"Method Not Allowed","status":405,"detail":"The GET method has not been defined for collections"}

Modifying Resource Class

As this resource is created automatically by Apigility, it might be you need to modify permissions before you can edit and save your changes.
$ sudo chmod go+w module/zfdemo/src/zfdemo/V1/Rest/User/UserResource.php
To continue, the easiest way to start is to see if you can fetch a collection of user entities and fetch a single entity of this user by providing an ID.
So modifying fetch() and fetchAll() methods in UserResource would allow us to do this.
/**
 * Fetch a resource
 *
 * @param  mixed $id
 * @return ApiProblem|mixed
 */
public function fetch($id)
{
    $service = new \User_Service_User();
    $result = $service->findUserById($id);
    return $result->toArray();
}

/**
 * Fetch all or a subset of resources
 *
 * @param  array $params
 * @return ApiProblem|mixed
 */
public function fetchAll($params = array())
{
    $service = new \User_Service_User();
    $result = $service->getAllUsers();
    return $result->toArray();
}
So when we access http://zf-apigility.local/v1/user/922 we receive the following result.
{"id":922,"name":"Devan Armstrong","email":"destiney.parker@yahoo.com","password":"galbibtlrvp","created":"1998-04-10 23:53:31","modified":"1993-10-18 04:29:35","_links":{"self":{"href":"http:\/\/zf-apigility.local\/v1\/user\/922"}}}
NOTE: The reason we return our objects as arrays is that if we use our objects, Apigility returns our ID as \u0000*\u0000_id":922, which doesn't work well. This is because of the hydrator that tries to access protected properties of the entity classes.

Let me know if it worked for you

As Apigility is fairly new and people have a bunch of ZF1 projects to maintain, I hope this tutorial allowed you to use your existing applications while still using your older ZF1 projects.

Let me know how this worked out for you and if you have encountered any difficulties setting it up.

Comments

Popular posts from this blog

Speeding up database calls with PDO and iterators

When you review lots of code, you often wonder why things were written the way they were. Especially when making expensive calls to a database, I still see things that could and should be improved.
No framework development When working with a framework, mostly these database calls are optimized for the developer and abstract the complex logic to improve and optimize the retrieval and usage of data. But then developers need to build something without a framework and end up using the basics of PHP in a sub-optimal way.

$pdo = new \PDO( $config['db']['dsn'], $config['db']['username'], $config['db']['password'] ); $sql = 'SELECT * FROM `gen_contact` ORDER BY `contact_modified` DESC'; $stmt = $pdo->prepare($sql); $stmt->execute(); $data = $stmt->fetchAll(\PDO::FETCH_OBJ); echo 'Getting the contacts that changed the last 3 months' . PHP_EOL; foreach ($data as $row) { $dt = new \DateTime('2015-04-…

PHP Arrays - Associative Arrays or Hash Maps

Associative array or hash maps are listings of key and value pairs with a posibility to nest additional keys and values. An associative array is a very powerful construct within PHP.

In our previous article we discussed simple arrays, which in their turn are indexed associative arrays under the hood. Take the following example:

$array = [
'apple',
'banana',
'chocolate',
]; 

Is in fact an indexed associative array under the hood:

$array = [
0 => 'apple',
1 => 'banana',
2 => 'chocolate',
]; 

But associative arrays can be so much more than just an indexed array, and you will find many database operations returning arrays where the fields of a table are the keys in the array while their values are also the values within the array.

$productRowData = [
'product_id' => 1234,
'brand_id' => 321,
'product_name' => 'Our awesome product',
'prodcut_description' => 'This is our most awesome product.&#…

Deploy Docker containers fast to Microsoft Azure

DEPLOY DOCKER CONTAINERS FAST TO MICROSOFT AZURE It’s hard to ignore the fact thatDockeris a way to move forward for rapid application development, distributed architectures and microservices. For developersDockeroffers great advantages as they can build their containers specifically for the task they work on. They grab a base image of a container, modify it for their purpose and prepare the functionality inside the container. Quality, testing and security teams now have a single instance to look at and ensure all functional and regulatory requirements are met. System engineers now don’t have to worry about providing a system with the required specs as the container is already provisioned for that purpose. But where do you deploy yourDockercontainers? You can set up your existing bare metal infrastructure to allow them to run containers, but this also means you need to learn about securing your container infrastructure, which is not an easy task. Luckily “the cloud” offers container …