How to Call Custom Espo API Route from an Entity PHP Controller

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • josbourn
    Junior Member
    • May 2025
    • 4

    #1

    How to Call Custom Espo API Route from an Entity PHP Controller

    Hi all,

    Background:
    I have a custom module package/extension that I'm working on. The module has a couple entities, controllers, etc.. The primary entity (License) has some of it's "fields" as actual fields that are stored to the database, while other "fields" are hard-coded into the views and retrieved from an external API I created. Some of the actual database fields need to be updated each time the record is read (if there's actual changes) from this external API. Right now, it works relatively okay, but the License entity controller does so with a CURL request to the external API directly, which isn't exactly how I want it to work. To get away from the License entity controller making CURL requests directly, I created some custom API routes. Each route handles the request, then runs a service that actually executes the request to the external API and returns it's response. I did this so there is only one place that is ACTUALLY handling requests to the external API.

    Issue:
    Anyway, for the life of me I can't figure out a way for the License entity controller to execute an API request to one of these routes (without CURL) similar to how the views can with "Espo.Ajax...".

    Current Approach:
    Currently I have the License entity controller using the injectableFactory to create a new instance of my route action class (GetLicense), then running it's "process" function passing the License controller's "Request" variable. This techncially kinda works, but the route action returns "ResponseComposer::json($data)". When I run "getBody()" on that response, it doesn't actually return the body, it returns the "Psr7\Stream" object instead.

    Ideas:
    • One possible solution/idea is to create a second action function in the route action class that is essentially a duplicate of the "process" function, but returns raw data instead of using the ResponseComposer.
    • The other idea (less likely and far more work) is to create an entirely separate controller and service (Kinda a middleware, I think?) that essentially adds the functionality to do something similar to the views JS for "Espo.Ajax...", but for PHP. This would basically take a route and any params, create a "Request" object, run the route action, take the ResponseComposer response and convert it to whatever data it should be (ie. json, text, etc.).

    If anyone has any ideas, input, suggestions, etc. it would be greatly appreciated. This may be something that's already possible and I just can't figure it out looking at the GitHub.

    Code

    Routes ( "custom/Espo/Modules/License/Resources/routes.json" ):
    Code:
    [
        {
            "route": "/License/:id",
            "method": "get",
            "params": {
                "controller": "License",
                "action": "API",
                "id": "id"
            }
        },
        {
            "route": "/AdminLayer/GetLicense/:id",
            "method": "get",
            "params": {
                "id": "id",
                "action": "read"
            },
            "actionClassName": "Espo\\Modules\\License\\Api\\AdminLayer\\GetLicense"
        }
    ]

    License Entity Controller ( "custom/Espo/Modules/License/Controllers/License.php" ):
    PHP Code:
      
    <?php
    namespace Espo\Modules\License\Controllers;

    use 
    Espo\Core\Api\Action;
    use 
    Espo\Core\Api\Request;
    use 
    Espo\Core\Api\RequestWrapper;
    use 
    Espo\Core\Api\Response;

    use 
    Espo\ORM\Entity;
    use 
    Espo\ORM\EntityManager;
    use 
    Espo\Core\ORM\Repository\Option\SaveOption;

    use 
    Espo\Modules\License\Api\AdminLayer\GetLicense;

    use 
    stdClass;

    class 
    License extends \Espo\Core\Controllers\Record
    {

        
    /**
         * This is basically the equivalent of "getActionRead".
         */
        
    public function getActionAPI(Request $requestResponse $response): stdClass
        
    {
            
    // Get the current record collection data.
            
    $id $request->getRouteParam('id');
            
    $recordCollection $this->getActionRead($request$response);
            
    $licenseKey $recordCollection->key;

            
    // Get the the custom API route action object ( "/api/v1/AdminLayer/GetLicense/:id" ).
            
    $getLicenseRoute $this->injectableFactory->create(GetLicense::class);
            
            
    // Run the request. Returns a "Espo\Core\Api\Response" object.
            
    $license $getLicenseRoute->process($request);
            
            
    // Get the $license Response body. Currently returns an "\Slim\Psr7\Stream" object.
            
    $body $license->getBody();


            
    // Modifying $recordCollection
            
    return $recordCollection;
        }


    }


    GetLicense API Route Action ( "custom/Espo/Modules/License/Api/AdminLayer/GetLicense.php" ):
    PHP Code:

    <?php
    namespace Espo\Modules\License\Api\AdminLayer;

    use 
    Espo\Core\Api\Action;
    use 
    Espo\Core\Api\Request;
    use 
    Espo\Core\Api\Response;
    use 
    Espo\Core\Api\ResponseComposer;

    use 
    Espo\Core\Exceptions\BadRequest;
    use 
    Espo\Core\Exceptions\Error;

    use 
    Espo\Core\Di\InjectableFactoryAware;
    use 
    Espo\Core\Di\InjectableFactorySetter;

    use 
    Espo\Modules\License\Tools\Service;

    use 
    stdClass;

    class 
    GetLicense implements
        
    Action,
        
    InjectableFactoryAware
    {
        use 
    InjectableFactorySetter;

        private 
    $endpoint  'GetLicense';

        public function 
    __construct(private Service $service) {}

        public function 
    process(Request $request): Response
        
    {
            
    $params $request->getRouteParams();
            
    $service $this->getLicenseService();

            
    $licenseId $params['id'];
            
    $entity $service->getEntityById($licenseId);
            
    $key $entity->get('key');

            
    $apiRequestData = [
                
    'key' => $key
            
    ];

            try {
                
    $result $this->getLicenseService()->apiRequest($this->endpoint'GET'$apiRequestData);
            } catch (
    Error $e) {
                
    $error = [
                    
    'error' => $e->getMessage()
                ];
                return 
    ResponseComposer::json($error);
            }

            
    json_decode($result);
            if (
    json_last_error() !== JSON_ERROR_NONE) {
                return 
    $result;
            }

            
    $result json_decode($result);

            return 
    ResponseComposer::json($result);
        }

        private function 
    getLicenseService(): Service
        
    {
            return 
    $this->injectableFactory->create(Service::class);
        }

        
    /**
         * @throws BadRequest
         */
        
    private function fetchData(Request $request): stdClass
        
    {
            
    $data $request->getParsedBody() ?? null;
            if (!
    $data instanceof stdClass) {
                throw new 
    BadRequest("No data.");
            }

            return 
    $data;
        }
    }


    License Service ( "custom/Espo/Modules/License/Api/AdminLayer/GetLicense.php" ):
    PHP Code:


    <?php
    namespace Espo\Modules\License\Tools;

    use 
    Espo\Core\InjectableFactory;
    use 
    Espo\Core\Exceptions\Error;
    use 
    Espo\Core\Record\ServiceContainer;
    use 
    Espo\Core\Utils\Log;
    use 
    Espo\Core\Utils\Config;
    use 
    Espo\Core\Utils\Config\ConfigWriter;

    use Espo\ORM\Entity;
    use 
    Espo\ORM\EntityManager;

    use 
    RuntimeException;
    use 
    stdClass;

    class 
    Service
    {
        public function 
    __construct(
            private 
    InjectableFactory $injectableFactory,
            private 
    ServiceContainer $serviceContainer,
            private 
    Config $config,
            private 
    ConfigWriter $configWriter,
            private 
    EntityManager $entityManager,
            private 
    Log $log
        
    ) { }

        
    /**
         * Handle the incoming request (make sure it's what we want)
         *
         * @throws Error
         */
        
    public function apiRequest($endpoint$method, array $data): mixed
        
    {
            
    // Run the request.
            
    $result $this->runApiRequest($endpoint$method$data);

            return 
    $result;
        }

        
    /**
         * Run the API request (To custom routes).
         *
         * @throws Error
         */
        
    private function runApiRequest($endpoint$method, array $data): mixed
        
    {
            
    // Run CURL request to external API and return the response (json).

            
    return $response;
        }

    }
  • josbourn
    Junior Member
    • May 2025
    • 4

    #2
    Update:

    I ended up moving forward with the current implementation and just adding a function to the License server that gets the contents of the Response. This works for my use-case, but I'm still curious if anyone knows how to essentially replicate the JavaScript "Espo.Ajax.." functionality but in PHP.

    Comment

    Working...