Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Endpoint Default Connection Name 'App' #87

Closed
matthttam opened this issue Feb 21, 2020 · 7 comments
Closed

Endpoint Default Connection Name 'App' #87

matthttam opened this issue Feb 21, 2020 · 7 comments

Comments

@matthttam
Copy link

A driver with a class of 'App\Webservice\Driver\FreshService' ends up trying to load a datasource named 'app' instead of something like Webservice\FreshService pr Driver\FreshService. I've tracked down the issue to Webservice/src/Model/Endpoint.php function defaultConnectionName(): string

I'm new to this framework however the default behavior of looking for Datasource > app didn't seem quite right.

@matthttam
Copy link
Author

matthttam commented Feb 22, 2020

2020-02-22 18:17:41 Error: [Cake\Datasource\Exception\MissingDatasourceConfigException] The datasource configuration "app" was not found. in /home/bil/projects/freshserviceinventory/vendor/cakephp/cakephp/src/Datasource/ConnectionManager.php on line 203
Exception Attributes: array (
'name' => 'app',
)
Stack Trace:

  • /home/bil/projects/freshserviceinventory/vendor/muffin/webservice/src/Model/EndpointLocator.php:101
  • /home/bil/projects/freshserviceinventory/vendor/cakephp/cakephp/src/Datasource/ModelAwareTrait.php:128
  • /home/bil/projects/freshserviceinventory/src/Controller/TicketsController.php:24
  • /home/bil/projects/freshserviceinventory/vendor/cakephp/cakephp/src/Controller/Controller.php:524
  • /home/bil/projects/freshserviceinventory/vendor/cakephp/cakephp/src/Controller/ControllerFactory.php:79
  • /home/bil/projects/freshserviceinventory/vendor/cakephp/cakephp/src/Http/BaseApplication.php:229
  • /home/bil/projects/freshserviceinventory/vendor/cakephp/cakephp/src/Http/Runner.php:77
  • /home/bil/projects/freshserviceinventory/vendor/cakephp/authorization/src/Middleware/RequestAuthorizationMiddleware.php:102
  • /home/bil/projects/freshserviceinventory/vendor/cakephp/cakephp/src/Http/Runner.php:73
  • /home/bil/projects/freshserviceinventory/vendor/cakephp/authorization/src/Middleware/AuthorizationMiddleware.php:129
  • /home/bil/projects/freshserviceinventory/vendor/cakephp/cakephp/src/Http/Runner.php:73
  • /home/bil/projects/freshserviceinventory/vendor/cakephp/authentication/src/Middleware/AuthenticationMiddleware.php:124
  • /home/bil/projects/freshserviceinventory/vendor/cakephp/cakephp/src/Http/Runner.php:73
  • /home/bil/projects/freshserviceinventory/vendor/cakedc/users/src/Middleware/SocialEmailMiddleware.php:34
  • /home/bil/projects/freshserviceinventory/vendor/cakephp/cakephp/src/Http/Runner.php:73
  • /home/bil/projects/freshserviceinventory/vendor/cakedc/users/src/Middleware/SocialAuthMiddleware.php:124
  • /home/bil/projects/freshserviceinventory/vendor/cakephp/cakephp/src/Http/Runner.php:73
  • /home/bil/projects/freshserviceinventory/vendor/cakephp/cakephp/src/Http/Runner.php:77
  • /home/bil/projects/freshserviceinventory/vendor/cakephp/cakephp/src/Http/Middleware/CsrfProtectionMiddleware.php:132
  • /home/bil/projects/freshserviceinventory/vendor/cakephp/cakephp/src/Http/Runner.php:73
  • /home/bil/projects/freshserviceinventory/vendor/cakephp/cakephp/src/Http/Runner.php:58
  • /home/bil/projects/freshserviceinventory/vendor/cakephp/cakephp/src/Routing/Middleware/RoutingMiddleware.php:162
  • /home/bil/projects/freshserviceinventory/vendor/cakephp/cakephp/src/Http/Runner.php:73
  • /home/bil/projects/freshserviceinventory/vendor/cakephp/cakephp/src/Routing/Middleware/AssetMiddleware.php:68
  • /home/bil/projects/freshserviceinventory/vendor/cakephp/cakephp/src/Http/Runner.php:73
  • /home/bil/projects/freshserviceinventory/vendor/cakephp/cakephp/src/Error/Middleware/ErrorHandlerMiddleware.php:118
  • /home/bil/projects/freshserviceinventory/vendor/cakephp/cakephp/src/Http/Runner.php:73
  • /home/bil/projects/freshserviceinventory/vendor/cakephp/debug_kit/src/Middleware/DebugKitMiddleware.php:60
  • /home/bil/projects/freshserviceinventory/vendor/cakephp/cakephp/src/Http/Runner.php:73
  • /home/bil/projects/freshserviceinventory/vendor/cakephp/cakephp/src/Http/Runner.php:58
  • /home/bil/projects/freshserviceinventory/vendor/cakephp/cakephp/src/Http/Server.php:90
  • /home/bil/projects/freshserviceinventory/webroot/index.php:40

Request URL: /Tickets
Client IP: 127.0.0.1

So my implementation is very basic at the moment.

I've got a TicketsController with an index function that only does this:
$this->loadModel('Tickets', 'Endpoint');

I've created a src/Webservice/Driver/Freshservice.php driver which extends AbstractDriver and calls setClient in its initialize function.

I've created a Model/Endpoint/TicketsEndpoint that extends Endpoint and has nothing else in it.
I've created a src/Webservice/TicketsWebservice that extends Muffin\Webservice\Webservice\Webservice . This has a function _executeReadQuery like so:

`protected function _executeReadQuery(Query $query, array $options = [])
{

    $response = $this->getDriver()->getClient()->get('/tickets',[],[ 'auth' => ['username' => '', 'password' => 'X']]);
    //debug('here');
    if (!$response->isOk()) {
        return false;
    }
    //debug($response->getJson());
    $resources = $this->_transformResults($query->getEndpoint(), $response->getJson()['tickets']);
    //debug($resources);
    //return '';
    return new ResultSet($resources, count($resources));
}`

So for some reason my setup is trying to look in config/app.php for datasource -> app. If I add this to my datasources:
'app' => [ 'className' => 'Muffin\Webservice\Datasource\Connection', 'service' => 'App\Webservice\Driver\Freshservice', 'host' => 'mysite.freshservice.com/api/v2/', //'api_key' => '', ],
It actually works. But I really don't want "app" to be the name of the datasource and I don't see a way to change the configuration.

debug_kit shows that Muffin\Webservice\Model\EndpointLocator->get is being passed:
'Tickets' [ 'alias' => 'Tickets', 'className' => 'App\Model\Endpoint\TicketsEndpoint' ]

Cake\Datasource\ConnectionManager::get is being passed 'app'

As I mentioned earlier, I tracked down this 'app' argument to the defaultConnectionName(): string which is taking my class name 'App\Model\Endpoint\TicketsEndpoint', splitting by '\', reversing it, then array_split with 3, 2... which is just getting the word 'app'.

` public static function defaultConnectionName(): string
{
$namespaceParts = explode('\', static::class);
$plugin = array_slice(array_reverse($namespaceParts), 3, 2);

    return Inflector::underscore(current($plugin));
}`

I'm probably just doing something wrong or I need to add some sort of config somewhere but I can't figure out how to fix it.

@ADmad
Copy link
Member

ADmad commented Feb 23, 2020

Have you made App\WebService\FreshWebservice class? Also your driver class should be App\WebService\Driver\Fresh, proper casing and naming convention matters. Then you need datasource config named fresh in your app config.

Refer to the Github webservice for example https://github.com/cvo-technologies/cakephp-github

@matthttam
Copy link
Author

So the application api I am pulling from is called "Freshservice". Thanks for the help on understanding the need for a FreshserviceWebservice that is then extended by things like the TicketsWebservice. However, this didn't correct the issue.

The plugin is still trying to find a datasource called 'app'

Just to be clear, I am using the 3.0.0 beta of your plugin as I am in CakePHP 4. So I replicated the example GitHub webservice and updated all of the functions and namespaces. Still, the app is trying to find a datasource called 'app' instead of 'Freshservice'.

Here is my setup:
Freshservice Driver located in src\Webservice\Driver\Freshservice.php:
`namespace App\Webservice\Driver;

use Cake\Http\Client;
use Muffin\Webservice\Webservice\Driver\AbstractDriver;

class Freshservice extends AbstractDriver
{...`

Freshservice Webservice located in src\Webservice\FreshserviceWebservice.php:
`namespace App\Webservice;

use Muffin\Webservice\Datasource\Query;
use Muffin\Webservice\Datasource\ResultSet;
use Muffin\Webservice\Webservice\Webservice;

class FreshserviceWebservice extends Webservice
{`

Tickets Webservice located in src\Webservice\TicketsWebservice.php
`namespace App\Webservice;

use Muffin\Webservice\Datasource\Query;
use Muffin\Webservice\Datasource\ResultSet;
use Muffin\Webservice\Webservice\Webservice;

class TicketsWebservice extends FreshserviceWebservice
{
public function initialize(): void
{
parent::initialize();
}`

Tickets Endpoint located in src\Model\Endpoint\TicketsEndpoint.php
`namespace App\Model\Endpoint;

use Muffin\Webservice\Model\Endpoint;

class TicketsEndpoint extends Endpoint
{`

So, with these files in place, shouldn't it be looking for a datasource called 'freshservice'? Instead I'm getting the error: 'The datasource configuration app was not found in config/app.php.'

@matthttam
Copy link
Author

I thought I'd take another look and try to figure this out.
cake-4.x branch file: src/Model/Endpoint.php
Method defaultConnectionName() which is used when loaded with the EndpointLocator I think.

public static function defaultConnectionName(): string
    {
        $namespaceParts = explode('\\', static::class);
        $plugin = array_slice(array_reverse($namespaceParts), 3, 2);

        return Inflector::underscore(current($plugin));
    }

One endpoint I am using has the following static::class "App\Model\Endpoint\AssetsEndpoint"
The function above sets namespace parts by exploding that class name as such:
$namespaceParts = Array ( [0] => App [1] => Model [2] => Endpoint [3] => AssetsEndpoint )
Then we do array_reverse($namespaceParts); Which returns:
Array ( [0] => AssetsEndpoint [1] => Endpoint [2] => Model [3] => App )
This is passed into array_slice(thearray, 3, 2)
This returns Array ( [0] => App )

So, this function makes sense if I were to create a plugin... but I'm using it directly in my project to perform API calls. Is that not intended?

@matthttam
Copy link
Author

So I spent today learning how to put together a plugin... getting that working... then learning how to split it into its own repository... then submitting it to packagelist. I now have a working freshservice API plugin requiring your plugin. It is now looking for the proper config name 'freshservice' instead of 'app'.

https://bitbucket.org/matt_henry_ops/freshservice

It is still very much a work in progress but this is working so far! Sorry for any confusion on my part. I thought I could just use the webservice plugin directly in my regular application... If I should be able to do this then the above code should either account for this or documentation on how to set the proper config name would be good.

Thanks!

@ADmad
Copy link
Member

ADmad commented May 21, 2020

Thanks for providing the detailed analysis and congrats on learning all the new stuff related to plugin development and publishing, that will definitely help you in future 🙂.

So when loading an endpoint class we don't have any info about which webservice it's related to and what connection it should use. Hence we have to resort to guess work based on the FQCN to get the connection name.

We could try to improve the connection name guessing or just clarify that users should override the Endpoint::defaultConnectionName() and make it return the connection to use. Since we don't really have much documentation the endpoint class under Create an endpoint (optional) section in readme could include the defaultConnectionName() method.

Given all the various classes needed for a webservice I would recommend organizing them using a plugin regardless. One can make an "app plugin" and doesn't necessarily need to publish it to packagist.

@ADmad
Copy link
Member

ADmad commented Dec 27, 2021

Closed by #88

@ADmad ADmad closed this as completed Dec 27, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants