Return to docs home page

Service Commands

Service commands are PHP classes that respond to incoming requests posted from the client browser. Usually they will be posted from a TypeScript ViewModel class.

The intention is for service commands to be the remote entry point for back-end services written in PHP and SQL database operations.

For a very simple example, See

The Call

Here's an example service call from a view model;

let me = this;
let request = {"tester" : testerName,
                "contextValue": this.contextValue()};
me.services.executeService('HelloWorld', request,
    // success function
    function (serviceResponse: Peanut.IServiceResponse) {
        if (serviceResponse.Result == Peanut.serviceResultSuccess) {
            let response = serviceResponse.Value;
            alert(response.message);
        }
    }
)  // fail function
    .fail(() => {
    alert('OOPS!!!!!!!!!!')
})  // always function
    .always(() => {
    me.application.hideWaiter();
});

(Note that the actual example file demonstrates the use of translation functions. We left that out here in order to focus in on the service command process itself.)

We assign a variable 'me', to the 'this' reference. This is a recommended practice because in a closure, such as success, fail, and always functions that follow, 'this' may to not refer to the view model. Using this technique, we can always use 'me' to reference any data or functions of the view model.

Next we compose a request object to be sent as input to the service command. This can be any kind of json object. Here we reference a local variable and a Knockout observable to get the data.

let request = {"tester" : testerName,
                "contextValue": this.contextValue()};

To initiate the request, we call

 me.services.executeService('HelloWorld', request, ...

The me.services refers to the ServiceBroker class. [3] This object handles all remote service calls using the jQuery Ajax library [references].

The first parmater, the 'serviceName', is a shorted version of the PHP ServiceCommand object on the server. In this case 'HelloWorld' is expanded to HelloWorldService command. In most cases we preceed the serviceName with an encoded version of the namespace as well. See the section below Namespaces in executeService

The next parameter is the request object (or null).

Then we have an anonymous function that executes at the successful conclusion of the service call.

    function (serviceResponse: Peanut.IServiceResponse) {
        if (serviceResponse.Result == Peanut.serviceResultSuccess) {
            let response = serviceResponse.Value;
            alert(response.message);
        }
    }

This function receives the serviceResponse object returned from the service. The serviceResponse.Result property tells us how things went. If the service fails in some way the serviceResponse will contain messages, which will automatically be displayed and the result code will be something other than serviceResultSuccess.

The serviceResponse.Value property contains the main data payload for a successful execution. and the remaining body of the function does what ever it needs to do for the view model. Usually this means assigning some observables with the resulting data.

The Service Command handles the request

On the server side, after checking for the secure authenticity of the request, the service command object is instantiated and the .run() method is invoked.

    protected function run()
    {
        // 1. retrieve the request object that was sent from the view model
        $request = $this->getRequest();

        // 2. Check for problems.
        if (empty($request)) {
            $this->addErrorMessage('No request received.');
            return;
        }
        if (empty($request->tester)) {
            $this->addErrorMessage('Tester name not received.');
            return;
        }

        // 3. Translate the context code, 
        $context = TVmContext::GetContext($request->contextValue ?? null);

        // 4. show some messages.
        $this->addInfoMessage('Hello World from: '.$request->tester);
        $this->addInfoMessage("Input value is:".$context->value);
        $this->addInfoMessage("Shared context:".$context->shared);

        // 5. create and return the response object
        $responseValue = new \stdClass();
        $responseValue->message = "Greatings earthlings from ".$request->tester;
        $this->setReturnValue($responseValue);
    }

Explanation:

  1. getRequest() retrieves the data sent by the ViewMode.
  2. addErrorMessage() sets a message the will automatically be displayed by the view model. It also sets the result value to 'serviceResultError'. we can just return now because nothing to be done without input.
  3. This is optional. TVmContext::GetContext() retrieves server side data associated with the view model. More about that later.
  4. Add some messages. These will be automatically displayed by the view model.
  5. Create and return the data:

Namespaces in executeService

In order to instantiate the service command object, the service request handling code constructs a full class name from the serviceName parameter. Here are examples of how the full namespace is constructed.

HelloWorld:

In this case a default namespace is retrieved from the application/config/settings.ini file

[services]
applicationNamespace='\Application\fma'

Since service command classes are always located in a 'services' sub-directory, '\services' is appended to the namespace. The namespace and class name in the PHP file will be:

namespace Application\fma\services;
class HelloWorldCommand extends \Tops\services\TServiceCommand

peanut.peanut-permissions::GetPermissions:

In most cases we prefix the service name with the namespace, followed by "::". This namespace is encoded for json compatibility in the remote service call. Words separated by '.' are given initial caps and the '.' becomes '\'.
Words separated by '-' are camel cased.

As a result, the PHP definition this one refers to is

namespace Peanut\PeanutPermissions\services;
class GetPermissionsCommand extends TServiceCommand

peanut.qnut-directory::messaging.InitializeDistributionLists

The service command class file might be located in a sub-directory to the services directory, in this case 'services\messaging'. In such a case we precede the service name following the '::' with the sub-path separated by '.'.

The PHP definition this one refers to is:

namespace Peanut\QnutDirectory\services\messaging;
class InitializeDistributionListsCommand extends \Tops\services\TServiceCommand

Given the namespace and classname the service handler can instantiate the service command class due to PHP autoloading. [1]

Creating a Service Command

  1. Pick a namespace and locate the corresponding directory.
    Pick one of the following: [2]
  1. Create the service command class in a PHP file. You may use this file as a template: web.root/packages/knockout_view/pnut/examples/servicecommand-template.txt
    And save it to the location you identified in step 2, with the filename

  2. Change the namespace and class names [4]

  3. Add the executeService call to your typescript view model file. You can use the content of this file as a template:
    web.root/packages/knockout_view/pnut/examples/service-call-template.txt


Notes:

1-Location and Autoloading

The root location for a service command is not always obvious as it depends on autoload initialization routines found in the startup code and entries in the [autoload] section of the application/config/setting.ini file. If you are looking for one, the easiest approach is to just do a content search for the class name.

For a detailed look at how namespaced are mapped to the directory structure, see: PHP Namespaces

2-Peanut Core Service Commands

Note that some service commands are found in sub-directories of web.root/packages/knockout_view/src. These classes are reserved for use of peanut core features.

3-Source files for handling service command requests

These are listed in order of their role in the service call sequence.

4-Case Sensitivity

If you are developing on a Windows computer, keep in mind that file names on the server operating system (Linux or other Unix type system) are case sensitive. Take care that your service command class name and the identifiers that refer to it have the same casing. Otherwise errors may occur in deployment that do not show up in development.

References