cult3

How to create an Active Record style PHP SDK Part 16

Oct 22, 2014

Table of contents:

  1. Time for some dirty testing
  2. The Save method
  3. Create new entity request
  4. Processing the Post Response
  5. Update existing entity request
  6. The Create method
  7. The Update method
  8. The Deletable trait and the Delete method
  9. Conclusion

A couple of weeks ago we looked at setting up the foundation for the persistence aspect of this Active Record style PHP SDK.

The Active Record pattern states that each model object should have public methods for creating, updating, saving and deleting directly from the data store. Last week we looked at providing that public API using the Storable trait.

This week we’re going to look at actually sending the requests to the CapsuleCRM API.

Time for some dirty testing

In order to be 100% confident that our code is working, we need to actually start sending requests to the CapsuleCRM. Automated tests are all well and good, but you still need to do a bit of manually testing to ensure your requests are hitting the API correctly.

You should never write unit tests that actually make HTTP requests to the third-party API, but setting up an end-to-end test is really important so you can ensure that everything is working correctly.

To do this I usually just set up a dirty index.php in the root of the project and then dump the code that I want to test in there. I’m not going to commit this file to version control so it doesn’t really matter what goes in this file.

If you want to see the code making requests to the CapsuleCRM server, you will need to register an account so you can get an API key and a subdomain for authentication.

Once you have your authentication details you can create a index.php file and require the autoload.php file:

require_once "vendor/autoload.php";

// dirty testing code

This will allow you to instantiate the classes of this package so you can test the code for yourself.

The Save method

The first method we will tackle will be saving a model instance. The save() method will be part of the object’s public API, but we can also use it as part of the create() and update() methods.

The first thing to do in the save() method is check to ensure that the model object is considered valid.

If you remember back to this tutorial, we looked at adding a Validating trait that provided a validate() method.

Instead of allowing the developer to make an invalid request to the CapsuleCRM API, we can validate the object and allow them to catch the problem without wasting an HTTP request.

So the first thing to do in the save() method is to check to see if the current model is valid:

/**
 * Save the current entity
 *
 * @return bool
 */
public function save()
{
    if ($this->validate()) {

    }

    return false;
}

The save() method should return a boolean response, and so if the model is not valid we can return false.

The save() method can be called for both creating and updating a model object. However, the API expects a different HTTP requests for creating and updating resources.

In order to send the correct request we can use the isNewEntity() method from a couple of weeks ago.

/**
 * Save the current entity
 *
 * @return bool
 */
public function save()
{
    if ($this->validate()) {
        if ($this->isNewEntity()) {

        }
    }

    return false;
}

And finally, once we have determined if this is a new entity or not, we can delegate to one of the two private request methods:

/**
 * Save the current entity
 *
 * @return bool
 */
public function save()
{
    if ($this->validate()) {
        if ($this->isNewEntity()) {
            return $this->createNewEntityRequest();
        }

        return $this->updateExistingEntityRequest();
    }

    return false;
}

Create new entity request

Next we will look at writing the createNewEntityRequest() method for sending the POST request to create a new entity:

/**
 * Create a new entity request
 *
 * @return Model
 */
private function createNewEntityRequest()
{

}

The first thing we need to do is to generate the endpoint we need to send the request to. We can do that by using the persistableConfig() method that we wrote in this tutorial:

/**
 * Create a new entity request
 *
 * @return Model
 */
private function createNewEntityRequest()
{
    $endpoint = '/api/'.$this->persistableConfig()->create();
}

Next we can create the request using the model’s internal Connection object:

/**
 * Create a new entity request
 *
 * @return Model
 */
private function createNewEntityRequest()
{
    $endpoint = '/api/'.$this->persistableConfig()->create();

    $this->id = $this->connection->post($endpoint, $this->toJson());
}

To make the request we need to pass in the $endpoint and the model object as JSON. If you missed the article we we added the ability to serialise model objects to JSON, you can read that here.

When the new entity is created in the API it will be given a unique identifier. We need to return this id and save it on the model object. This means if the developer makes further changes to this model object and then calls the save() method again, the request will go to the update endpoint, instead of the create endpoint.

Finally, we can return true if everything goes smoothly:

/**
 * Create a new entity request
 *
 * @return Model
 */
private function createNewEntityRequest()
{
    $endpoint = '/api/'.$this->persistableConfig()->create();

    $this->id = $this->connection->post($endpoint, $this->toJson());

    return true;
}

Processing the Post Response

When the CapsuleCRM successfully creates a new record, you will be returned a 201 and a URL to the new record. Unfortunately the API does not return a copy of the new record, so in order to get the id, we need to use a regex on the URL.

To do that we can add the following method to the Connection class:

/**
 * Return the id of the new entity
 *
 * @param Response $response
 * @return int
 */
private function processPostResponse(Response $response)
{
    if (isset($response->getHeaders()['Location'])) {
        preg_match('/\/(?<id>\d+)$/', $response->getHeaders()['Location'][0], $matches, PREG_OFFSET_CAPTURE);

        return (int) $matches['id'][0];
    }

    return true;
}

This method checks to see if the Location header is set. If it is it will use the regex to return the id.

We can then wrap the post() method to return the response through the processPostResponse() method:

/**
 * Send a POST request
 *
 * @param string $url
 * @param string $body
 * @return Guzzle\Http\Message\Response
 */
public function post($url, $body)
{
    return $this->processPostResponse($this->client()->post($url, ['body' => $body]));
}

This isn’t a pretty solution, but it will do the job for now.

Update existing entity request

Updating an existing entity is pretty much the same as creating a new entity:

/**
 * Update an existing request
 *
 * @return Model
 */
private function updateExistingEntityRequest()
{
    $endpoint = '/api/'.$this->persistableConfig()->update();

    $this->connection->put($endpoint, $this->toJson());

    return true;
}

The only differences are in the update method we need to send a PUT request and there is no need to retrieve the id from the response header.

The Create method

The create() method should allow the developer to create a new entity and save it in one go:

/**
 * Create a new entity
 *
 * @param array $attributes
 * @return Model
 */
public function create(array $attributes)
{
    $model = new static($this->connection, $attributes);

    $model->save();

    return $model;
}

In this method I’m simply creating a new $model object as you normally would, and then calling the save() method and returning it.

The Update method

The update() method is even easier to implement because all we need to do is to pass the $attributes to the fill() method and then call save():

/**
 * Update an existing entity
 *
 * @param array $attributes
 * @return Model
 */
public function update(array $attributes)
{
    $this->fill($attributes);

    return $this->save();
}

The Deletable trait and the Delete method

Finally we can create the Deletable trait and define the delete() method:

<?php namespace PhilipBrown\CapsuleCRM\Persistance;

trait Deletable
{
    /**
     * Delete an existing model
     *
     * @return bool
     */
    public function delete()
    {
        $endpoint = "/api/" . $this->persistableConfig()->delete();

        if ($this->connection->delete($endpoint)) {
            $this->id = null;

            return true;
        }

        return false;
    }
}

In this method we once again generate the $endpoint and pass it to the Connection object. When sending a DELETE HTTP request we don’t need to provide a request body and so we only need to pass the $endpoint.

Next we can set the id property to null and return a boolean.

To empower any model class with the ability to delete records, we can now simply include the Deletable trait.

Conclusion

In today’s tutorial we’ve looked at implementing the persistence methods in this Active Record style PHP SDK. It should now be possible to add, edit and delete records from your CapsuleCRM account directly from your PHP code.

We’ve covered a hell of a lot already in this series. When I started writing these posts I had no idea I would get to 16 posts and still not be finished.

The last thing we need to cover is the relationships between entities. As this is an Active Record style SDK, we should be able to access related items directly from each model instance.

However, I’m totally burned out with writing this series, and so I’m going to pause and take a breather. I’m also unsatisfied with much of the code in this SDK. I think an important part of developing is where you write kinda crappy code initially to get a better feel for the problem you are tackling.

I’m going to step away from developing this PHP package for a while so I can take a broader look at how the code should be implemented. I hope to return to this series with a fresh perspective on how to best solve this problem.

Hopefully this will be a good learning experience for you as I refactor my code to make it it a whole lot better!

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.