Oct 22, 2014
Table of contents:
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.
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 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;
}
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;
}
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.
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 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 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();
}
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.
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!