Hello all,
My first problem was that I wanted my model Temple had the resource type worship:temple instead of temples or temple. I could finally fix it with some Resolver classes, but I think that should be a lot easier.
My second problem I could not solve yet. Now, my Temple class is linked to worship:temple, but I also forced to browse to /worship/v1/worship:temple/ instead of /worship/v1/temples/.
How can I fix this?
Thanks in advance
Hi. Yeah, good question, I'm not sure this is supported at the moment. Technically the URL is meant to have the resource name, so worship:temple in the URL is correct (it would of course be URL encoded as worship%3Atemple).
Customising this is probably not going to be do-able in the current version, because there's too much resolution going on in different places, all via the resource type's name. I'm starting to think the revamp that is in the pipeline is going to have to remove all the resolution, as people do not seem as determined to stick to a single convention as I do! there's conversations going on in the Slack channel as to how to revamp in a way that I can remove the resolution, which would allow a lot greater customisation along the lines you're trying to do.
I have written a custom Resolver that converts it in some way. It works, but also sucks, because I cannot rely 100% on it at the moment. I would like to see this in a release one day, because the resourceType is not necessary the same as the pathName.
I'm doing a POST /temples to add a temple:
{
"data":{
"type": "worship:temple",
"attributes":{
"name": "Name of the temple"
}
}
}
However, then the server only accepts temples, but it should accept worship:temple:
{
"errors":[
{
"status":"409",
"title":"Not Supported",
"detail":"Resource type worship:temple is not supported by this endpoint.",
"source":{
"pointer":"\/data\/type"
}
}
]
}
@lindyhopchris When do you think you will have a version that supports the situation that the URL and the type of the resource are different? My API is slowly growing bigger and I certainly see the need of this in the future. I successfully made a resolver that works in some way, but it is still very cracky. Because this project is big and not mine, it is hard to implement the future myself (and maybe you have something else in mind that works better to support the feature).
@ben221199 yeah I can probably look at this some point this weekend.
Can you just confirm exactly how you want this to work? I.e. you want the resource type to be worship:temple but you want the URL to be (e.g.) /api/temples?
What about the class names? Do you want the classes to be WorshipTemple\Schema or Temples\Schema? I think it's going to have to be WorshipTemple\Schema to make this work, but wanted to check with you.
I'm prepared to look at this but if it starts to get extremely complicated to wire it in, then I'll have to make a call as to how much work it's going to be. I'm actually at the moment working on the new version, so wasn't planning to do any more massive changes to this version.
I have a class Temple with a constant TYPE which has the value worship:temple. In my resolver factory I link this type to the model (it is also possible in the resources array in the config): Temple::TYPE => Temple::class. Because I have a custom resolver, I resolve my models to Worship\Temple\Schema. You are free to choose. One tip, maybe you can get class names by the class name of the model instead of the resource type. If there is a resource type given, using the resources array it is possible to convert the resource type to the corresponding model class name.
@lindyhopchris How are you doing?
Lets make a overview about the whole thing:
type and the class object:$resources = [
'accounts:user' = App\Models\Accounts\User::class,
'worship:temple' = App\Models\Worship\Temple::class,
'worship:broadcast' = App\Models\Worship\Broadcast::class,
];
Very simple, one model class for every type and one type for every model class.
At the moment, the resource route function is like this:
public function resource(string $resourceType, array $options = []): ResourceRegistration
Lets say we change it to something like this:
public function resource(string $uri, string $type, array $options = []): ResourceRegistration
Then you can do this:
//Registers a User endpoint at '/users'.
$api->resource('/users','accounts:user');
//Registers a User endpoint at '/myusersendpoint'.
$api->resource('/myusersendpoint','accounts:user');
//Or we can even do this:
$api->resource('/broadcasts','accounts:user');
//A endpoint for 'worship:broadcasts' would be more logical here, but I don't want to 馃槢.
The resolver resolves strings and class names to other strings or class names, like the Adapter, Schema and Validator.
We had the following types in the resolver:
App\Models\Accounts\User::class)type of the resource and also the URI of the resource)But now we need the following types in the resolver:
User::class; at the moment it is called type in the resolver)accounts:user; at the moment it is called resourceType in the resolver)When you get the class name of a adapter, schema or validator by the class name of the model, it will be something like this:
public function getAdapterByModel($model){
return 'App\JsonApi\Modules\'.class_basename($model).'\Adapter';
}
If you want to get the adapter, schema or validator by the type of the resource, you can do this:
public function getAdapterByType($type){
$model = $this->resources[$type];
return $this->getAdapterByModel($model);
}
type and resourceType next to each other.Use model and type, because resourceType and type are actually the same.
In (3), the $this->resources has the content I defined in (1).
@ben221199 thanks for your additional comments.
I'm looking at this today. If it doesn't turn into a huge job I'll get it added to the existing package today.
Actually, I've just had a look back at the spec - and my reading of it is that : is not allowed in a resource type name.
The spec says:
The values of type members MUST adhere to the same constraints as member names.
https://jsonapi.org/format/#document-resource-object-identification
Then in the Section on Member Names:
Member names MUST contain only the allowed characters listed below.
Then the allowed characters listed here do not mention :.
So I'm not sure the package needs to support this because my reading of the spec is that the : character can't be used in the type value. Have you read anything differently or seen anything that says this is allowed?
@lindyhopchris The problem is not about me using the : in my resource type. If it isn't allowed, then I will change it in my API. The issue is that it is possible for the URI and the resource type to be different.
I think I will change the : to -, _ or . Thanks for noticing.
Cool, yeah ok I'm definitely happy to add changing the URI to something different from the resource type. Will crack on with that now.
@ben221199 I've pushed a branch:
composer require "cloudcreativity/laravel-json-api:dev-issue507"
Solution:
JsonApi::register('default')->routes(function ($api) {
$api->resource('posts')->uri('blog_posts');
});
Can you give it a go and let me know if this solves the issue for you?
You made the URI optional? 馃
?? Yeah, if you don't call uri it uses the resource type as the URI (the current behaviour).
Is that what you're querying?
Means the change is non-breaking and can be released as a minor version, not major.
Great work! It works for me. The optional URI is also nice, so that it has a fallback to the resource type when not giving a different URI.
For now, the Resolver is still confusing, because of the type and resourceType used in it (see (4) in https://github.com/cloudcreativity/laravel-json-api/issues/507#issuecomment-729034379). However, it would be a breaking change for now to fix it.
@lindyhopchris I found one problem:
When I have my model worship:broadcast with the URI /broadcasts and I have a relationship 貌n it named temple, the URI should be /broadcasts/<ID>/temple (where /broadcasts is my custom URI and temple is the name of the relation that I defined in the Schema). However, at the moment it will give /worship:broadcast/<ID>/temple.
I can solve it with defining $selfSubUrl in the schema, but that shouldn't be necessary when having a future to set a custom URI with only one single function ->uri().
I made a pull request #571 that fixes this bug. Maybe some things could be improved.
@ben221199 the solution to this is to set the $selfSubUrl property on your schema. It exists for exactly this use-case, i.e. overriding the default of the resource's sub url being /workship:broadcast.
In your case, on the schema you need to set it to /broadcasts.
I'm going to close your PR because it's not needed, plus it's not the correct solution. It attempts to resolve the Route, but the Route might not exist if, for example, JSON:API resources are being serialized for broadcasting.
Hmmm, the problem is then that things will break if I want to use my resource worship:broadcasts on multiple URIs, like this:
$api->resource(Broadcast::TYPE)->uri('broadcasts');
$api->resource(Broadcast::TYPE)->uri('myBroadcasts');
$api->resource(Broadcast::TYPE)->uri('yourBroadcasts');
So, the conclusion is then that the $selfSubUrl should be resolved somehow when I don't give it a value. If it is an other solution then my PR, it is also fine, but $selfSubUrl is not the solution.
Wait, okay, when not using it with Routes, you are correct, but I check if the Route is null and if so, return the resource type instead.
Maybe it should resolve like this:
$selfSubUrl set in the schema. If there is not, go to the next.$selfSubUrl set, so you generate the URL by getting the resource type.Sorry, but to be completely clear - the route registration, and the serialization of resource to JSON (done by the Schemas) are serparate things. I expect you to have to update it in both places.
Use uri() to specify the URI fragment when registering the route.
Then update selfSubUrl to the value it needs to be to generate the correct URIs when serializing objects to JSON.
There's no inter-connection between the two. If you're not using the default behaviour, you'd need to update both. The reason there is no inter-connection is the package supports serializing objects to JSON outside of HTTP requests. E.g. when broadcasting JSON during queued jobs. This means the schema cannot resolve the URI from the route class - because outside of HTTP requests, there would be no current route.
If you do update both, do you get the correct behaviour? I.e. the Laravel routes registered correctly, plus URLs correctly serialized in the response JSON?
because outside of HTTP requests, there would be no current route.
Because of that, I check if the route is null.
I'm thinking 馃槀
Um the other reason it won't work even if there's a route... there's a Schema class for each resource type. The current route only has the details of the resource type being requested. If you read it off the route, then every resource will have the same resource type!
I'd really recommend not doing what you're trying to do!
@ben221199 just want to check my comment above makes sense, and that you're able to update the $selfSubUrl on each Schema in your API?
Would be good to get this issue closed as I think this is done now.
Yes, it could be closed. When will it be part of the official version?
Great, thanks for confirm - and thanks for raising the issue in the first place. It's a good addition being able to customise the URI fragment for a resource type.
It was tagged this morning - v3.2.0
Most helpful comment
@ben221199 I've pushed a branch:
Solution:
Can you give it a go and let me know if this solves the issue for you?