4

I am doing a lot of work with SOAP Api in PHP and the objects that are returned are quite big. I am then updating the objects from local db changes and pushing them back.

So more often than not I have a large object returned from a db query and a large object retrieved from a SOAP call and I have to update the one from the other.

So I end up with a lot of this:

$api_object->Organisation->Location[0]->AddressLine1 = $db_object->MainAddress->address_line_1;
$api_object->Organisation->Location[0]->AddressLine2 = $db_object->MainAddress->address_line_2;

$api_object->Organisation->Location[1]->AddressLine1 = $db_object->SecondaryAddress->address_line_1;
$api_object->Organisation->Location[1]->AddressLine2 = $db_object->SecondaryAddress->address_line_2;

And so on, and so on with many more elements! And as you can see the names of the fields don't often match in naming or depth.

I just wondered if anyone had any suggestions how to do it better.

1

Along with Robert Harvey's advice, rather than 'setting' an existing API object's properties from an instance of a DB Object directly where you need it to happen, why not just 'create' a brand new API object using a factory that uses your DB object as a template?

class ApiObjectFactory 
{
    public static function createFromDbObject($dbObject, ApiFactoryInterface $factory)
    {
        return $factory->createFromObject($dbObject);
    }
}

The secondary factory you pass in to this one will be the one responsible for creating individual items. The purpose of this outer factory is to encapsulate all of these secondary factories to a single spot (so anywhere else that needs to create an API Object from a DB Object will call here).

interface ApiFactoryInterface
{
    public function createFromObject($dbObject);
}

This interface is just setting up the contract that your other type factories will adhere to. This is not strictly necessary, but is good for future proofing and setting up the expectations on exactly what these objects will be doing.

class  OrganizationApiFactory implements ApiFactoryInterface
{
    public function createFromObject($dbObject)
    {
        $organization = new StdClass;
        $organization->Location = [];
        $organization->Location[0] = $this->createAddress($dbObject->MainAddress);
        $organization->Location[1] = $this->createAddress($dbObject->SecondaryAddress);

        return $organization;
    }

    protected function createAddress(StdClass $address)
    {
        $returnAddress = new StdClass;
        $returnAddress->AddressLine1 = $address->address_line_1;
        $returnAddress->AddressLine2 = $address->address_line_2;

        return $returnAddress;
    }
}

If you are able to take advantage of dependency injection, you could offload the filling of the organization's addresses to an AddressApiFactory and be able to completely decouple this process by injecting related factories into the organization factory.

class  OrganizationApiFactory implements ApiFactoryInterface
{
    public function __construct(AddressApiFactory $addressFactory)
    {
        $this->addressFactory = $addressFactory;
    }

    public function createFromObject($dbObject)
    {
        $organization = new StdClass;
        $organization->Location = [];
        $organization->Location[0] = $this->addressFactory->createFromObject($dbObject->MainAddress);
        $organization->Location[1] = $this->addressFactory->createFromObject($dbObject->SecondaryAddress);

        return $organization;
    }
}

class AddressApiFactory implements ApiFactoryInterface
{
    public function createFromObject($dbObject)
    {
        $returnAddress = new StdClass;
        $returnAddress->AddressLine1 = $address->address_line_1;
        $returnAddress->AddressLine2 = $address->address_line_2;

        return $returnAddress;
    }
}

This approach does a couple things for you:

  1. It separates the concerns out for all of your different API model 'types'. If you are typing the same things over and over again, that is a code smell. You should endeavor to create small, functional units that do a single thing and a single thing well. An organization factory in this context knows precisely how to take your database object and convert it into whatever format you need to send it over to your API. It has absolutely zero knowledge of anything else.

  2. If done correctly, it can greatly increase the readbility and maintainability of your code. When complete, you should be able to save any object to your API given an existing object from your database with just a handful lines of code. Less code to debug = faster debugging. Less code to debug = less debugging, since less code has a tendency to break less often.

An example of how you could update an API Object using this approach:

// Simulating fetching an object from the api and loading your corresponding 
// object from the database
$myApiObject = getObjectFromApi();
$myDbObject = getObjectFromDb($myApiObject);

// Simulate updating your object locally
$myDbObject->address_line_1 = '111 Testing Dr';

// Now you need an API Object to save...
$factory = new ApiObjectFactory;
$apiObjectToSave = $factory->createFromDbObject($myDbObject, new OrganizationApiFactory);

// Save your updated data to your API
sendApiSaveRequest($apiObjectToSave);

// ... or even better ...
$apiObjectToSave->save();
1
  • Wow. Yeah you're right. I do need to rethink. Thanks for the in depth response.
    – mattl
    Mar 23 '15 at 17:55
6

Create alias references for the Location and Address objects to shorten the assignments.

$loc = $api_object->Organisation->Location;
$main = $db_object->MainAddress;

$loc[0]->AddressLine1 = $main->address_line_1;
$loc[0]->AddressLine2 = $main->address_line_2;
1
  • Yeah I do that in places where it makes sense - I was just hoping for more!
    – mattl
    Mar 19 '15 at 9:03
1

This feels like a "service" class or an implementation of the "unit of work" pattern would be beneficial. I'm leaning towards Unit of Work here.

You can define multiple "unit of work" classes in a hierarchy to break up the updates. Plus you can define additional classes for updating the $db_object with values from the $api_object. While this adds complexity, it does decouple the two layers giving you explicit integration points, and it reflects the transactional nature of what you are trying to accomplish. It gives you a tree structure of objects that do the work for you in an organized and isolated fashion.

class UpdateApiUnitOfWork {
    private $update_organization;

    public function UpdateApiUnitOfWork() {
        $this->update_organization = new UpdateApiOrganizationUnitOfWork();
    }

    public function execute($api_object, $db_object) {
        $this->update_organization->execute($api_object->Organization, $db_object);
    }
}

class UpdateApiOrganizationUnitOfWork {
    private $update_location;

    public function UpdateApiOrganizationUnitOfWork() {
        $this->update_location = new UpdateApiLocationUnitOfWork();
    }

    public function execute($organization, $db_object) {
        $this->update_location->execute($organization->Location[0], $db_object->MainAddress);

        if (isset($organization->Location[1]))
            $this->update_location->execute($organization->Location[1], $db_object->SecondaryAddress);

        // ...
    }
}

class UpdateApiLocationUnitOfWork {
    public function execute($location, $address) {
        $location->AddressLine1 = $address->address_line_1;
        $location->AddressLine2 = $address->address_line_2;
        // ...
    }
}

Using this "unit of work" pattern becomes very easy:

$api_object = // Get from the Web API
$db_object = // Get from the database

$updater = new UpdateApiUnitOfWork();
$updater->execute($api_object, $db_object);

If you don't need to update the whole chain of objects, you can pick and choose which unit of work you want to apply:

$updater = new UpdateApiOrganizationUnitOfWork();
$updater->execute($api->Organzation, $db_object);

After that, you just need to call the Web API to update the information, or call into your data access layer to persist those changes to the database.

2
  • Not heard of the unit of work pattern before. Thanks for the heads up.
    – mattl
    Mar 24 '15 at 18:22
  • I've seen the Unit of Work Pattern referred to in the .NET community mostly (cringes as Ruby, PHP, Java, JavaScript, Python and Perl communities prepare to throw rotten tomatoes at me) Mar 24 '15 at 19:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.