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:
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" ):
License Entity Controller ( "custom/Espo/Modules/License/Controllers/License.php" ):
GetLicense API Route Action ( "custom/Espo/Modules/License/Api/AdminLayer/GetLicense.php" ):
License Service ( "custom/Espo/Modules/License/Api/AdminLayer/GetLicense.php" ):
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 $request, Response $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;
}
}
Comment