calcinai / xero-php

A php library for the Xero API, with a cleaner OAuth interface and ORM-like abstraction.
MIT License
359 stars 262 forks source link

Implementing PayrollUK employee employment #457

Open dextermb opened 5 years ago

dextermb commented 5 years ago

As mentioned in #322 I am working on implementing PayrollUK in my fork, but I am having issues understanding how to implement employment on an employee.

PayrollUK\Employee
# <snip>

class Employee extends Remote\Model
{
   # <snip>

    /**
     * Get a list of properties
     *
     * @return array
     */
    public static function getProperties()
    {
        return [
            # <snip>
            'employment' => [false, self::PROPERTY_TYPE_OBJECT, 'PayrollUK\\Employee\\Employment', false, true],
        ];
    }

    /**
     * @return Employment
     */
    public function getEmployment()
    {
        return $this->_data[ 'employment' ];
    }

    /**
     * @param Employment $value
     * @return $this
     */
    public function setEmployment(Employment $value)
    {
        $this->propertyUpdated('employment', $value);
        $this->_data[ 'employment' ] = $value;

        return $this;
    }

   # <snip>
}

I am currently attempting to use save directly (4th element of property array as true), but I run into an exception:

get_class() expects parameter 1 to be object, array given

Which is being thrown in Application.php#400. Any ideas on how to resolve this without breaking old models?

I've currently got Employee and PayRollCalendar implemented.

dextermb commented 5 years ago

Here's an example of how I am attempting to set an employment:

/** @var \XeroPHP\Models\PayrollUK\PayRollCalendar[] $items */
$items = $xero->load(\XeroPHP\Models\PayrollUK\PayRollCalendar::class)->execute();
$calendarID = $items[ 0 ]->getID();

/** @var \XeroPHP\Models\PayrollUK\Employee[] $items */
$items = $xero->load(\XeroPHP\Models\PayrollUK\Employee::class)->execute();
$employee = $items[ 5 ];

$employment = (new \XeroPHP\Models\PayrollUK\Employee\Employment($xero))
    ->setPayrollCalendarID($calendarID)
    ->setStartDate(now())
    ->setEmployeeNumber('09');

$employee->setEmployment($employment);

$employee->save();
dextermb commented 5 years ago

I've managed to get this working. Here's what I've updated in my fork:

XeroPHP\Application
abstract class Application
{
    # <snip>

    /**
     * @param Remote\Model $object
     * @throws Exception
     */
    public function saveRelationships(Remote\Model $object)
    {
        return $this->savePropertiesDirectly($object);
    }

    /**
     * Function to save properties directly which do not update via a POST.
     *
     * This is called automatically from the save method for things like
     * adding contacts to ContactGroups
     *
     * @param Remote\Model $object
     * @throws Exception
     */
    private function savePropertiesDirectly(Remote\Model $object)
    {
        foreach ($object::getProperties() as $property_name => $meta) {
            if ($meta[ Remote\Model::KEY_SAVE_DIRECTLY ] && $object->isDirty($property_name)) {
                $property_objects = $object->$property_name;

                if (is_object($property_objects)) {

                    /** @var Remote\Model $model */
                    $model = $property_objects;

                    $url = new URL(
                        $this,
                        sprintf(
                            '%s/%s/%s',
                            $object::getResourceURI(),
                            $object->getGUID(),
                            $model::getResourceURI()
                        ),
                        $model::getAPIStem()
                    );

                    $method = $model::getCreateMethod() ?? Request::METHOD_PUT;
                    $data = $model->toStringArray(true);

                    $request = new Request($this, $url, $method);
                    $request->setBody(json_encode($data), Request::CONTENT_TYPE_JSON);
                    $request->send();

                    $response = $request->getResponse();

                    foreach ($response->getElements() as $key => $element) {
                        if ($response->getErrorsForElement($key) === null) {
                            $method = 'set' . ucfirst($key);

                            if (method_exists($model, $method)) {
                                $model->{$method}($element);
                                $model->setClean($key);
                            }
                        }
                    }

                    continue;
                }

                /** @var Remote\Model[] $property_type */
                $property_type = get_class(current($property_objects));

                $url = new URL(
                    $this,
                    sprintf(
                        '%s/%s/%s',
                        $object::getResourceURI(),
                        $object->getGUID(),
                        $property_type::getResourceURI()
                    )
                );

                $method = Request::METHOD_PUT;
                $request = new Request($this, $url, $method);

                $property_array = [];

                /** @var Object[] $property_objects */
                foreach ($property_objects as $property_object) {
                    $property_array[] = $property_object->toStringArray(false);
                }

                $root_node_name = Helpers::pluralize($property_type::getRootNodeName());

                $request->setBody(Helpers::arrayToXML([$root_node_name => $property_array]));
                $request->send();

                $response = $request->getResponse();

                foreach ($response->getElements() as $element_index => $element) {
                    if ($response->getErrorsForElement($element_index) === null) {
                        $property_objects[ $element_index ]->fromStringArray($element);
                        $property_objects[ $element_index ]->setClean();
                    }
                }

                //Set it clean so the following save might have nothing to do.
                $object->setClean($property_name);
            }
        }
    }

    # <snip>
}
Remote\Model
abstract class Model implements ObjectInterface, \JsonSerializable, \ArrayAccess
{
    # <snip>

    /**
     * @throws Exception
     * @throws \XeroPHP\Exception
     */
    public function saveRelationships()
    {
        if ($this->_application === null) {
            throw new Exception(
                '->saveRelationships() is only available on objects that have an injected application context.'
            );
        }

        return $this->_application->saveRelationships($this);
    }

    # <snip>
}

Here's the full example:

/** @var \XeroPHP\Models\PayrollUK\PayRollCalendar[] $items */
$items = $xero->load(\XeroPHP\Models\PayrollUK\PayRollCalendar::class)->execute();
$calendarID = $items[ 0 ]->getID();

/** @var \XeroPHP\Models\PayrollUK\Employee[] $items */
$items = $xero->load(\XeroPHP\Models\PayrollUK\Employee::class)->execute();
$employee = $items[ 5 ];

$employment = (new \XeroPHP\Models\PayrollUK\Employee\Employment($xero))
    ->setPayrollCalendarID($calendarID)
    ->setStartDate(now())
    ->setNiCategory('A')
    ->setEmployeeNumber('09');

$employee->setEmployment($employment);
$employee->saveRelationships();