I've being building an API and I find myself in a situation where I want to reuse the models from the API in my webapp.
[query]<>[webapp]<>[models]<>[api]<>[models]<>[db]
In the webapp I'm creating models based on the output of the API.
In the API I'm creating models based on the output of the database.
Both models are identical to each other.
Is there a way I can do this properly without copy/pasting the models?
Or is there a way to avoid having the same models?
EDIT:
What I mean is that I'm using a PHP-API and a PHP-Frontend.
Both models in PHP-API and PHP-Frontend are equal to each other.
You could make a service within the API that returns the model data as its response. That way you can build a simple converter to change the models from PHP into some kind of JSON or other data structure format in the backend and transmit them.
You'll only have one copy of the actual models and will auto-generate the ones used by the frontend.
A very simple example to elaborate:
class Backend_Book {
public $title;
public $author;
}
class Backend_Recipe {
public $name;
public $ingredients;
public $steps;
}
Add to your api:
class Api_Get_Domain {
$domain = load_domain();
$json = [];
foreach( $domain as $domain_model ) {
$json[] = convert_model($domain_model);
}
return json_encode($json);
}
Call from your frontend:
$http.get('your/api/domain').then( function( response ) {
domain = response;
});
And then the idea is that "response" will be something like this:
response = [
{
model: "Book",
properties: [
"title", "author"
]
},
{
model: "Recipe",
properties: [
"name", "ingredients", "steps"
]
}
];
And now you have, in your frontend, a copy of the domain as used by the backend, that you won't need to update. Obviously this is a very basic example, you could add as much information to the domain models as you need, but the basic idea is to make the API provide its own domain to the users of the API.
Related
I am working on an CakePHP REST/CRUD based API. It uses $routes->setExtensions(['json']); within /config/routes.php to make the it's responses into json objects.
I am working with several objects that have complex schema's that I need to pre-process prior to to submitting to the CakeORM, in order to simplify the API integration for the end user.
For instance the following is the json blob that would be needed to be patched to the ORM using $this->ImportSettings->patchEntity($importSetting, $requestData[2]):
{
"id": 2,
"generic_setting": "Hello World",
"import_source_google_setting": null,
"import_source_csv_ordered_setting": null,
"import_source_csv_headed_setting": {
"id": 1,
"import_settings_id": 2,
"delimiterId": 1
},
"import_destination_user_setting": null,
"import_destination_asset_setting": {
"id": 2,
"import_settings_id": 2,
"defaultValueId": 1
}
}
There can be one of many sources and one of many designation settings defined on an import setting. To simplify this for the API user I am allowing them to submit, the following:
{
"id": 2,
"generic_setting": "Hello World",
"import_source_setting": {
"id": 1,
"import_settings_id": 2,
"delimiterId": 1
},
"import_destination_setting": {
"id": 2,
"import_settings_id": 2,
"defaultValueId": 1
}
}
I have written code into an event listener on beforeMarshal for ImportSesttings that is able to tell if the index "import_source_setting" belongs in in the tables "import_source_csv_headed_setting", "import_source_csv_ordered_setting" or "import_source_google_setting" and likewise with asset and user settings going into "import_destination_setting".
This works well for processing a re-organizing data in a request before it enters the ORM. However I would like to do the same thing now with the data before it is displayed, so the API user does not need to look at the addtional source and destination settings.
I have accomplished this through the use of middleware in a similar use case in another part of the system. However the middleware seems to be made to attach to routes, my uses seems more like something that should be tied to the model life cycle so it runs whenever an import settings is returned and properly modifies the output, even when nested.
Given what I am looking for, what part of Cake should I place this logic that re-organizes the json response on the ORM query result for a table in? Can your point me to documentation on this?
I came across an answer for this in another forum using CakePHP's calculated fields. It looks like the formatResults() function can be attached to a query with a callback to re-organize the results after the query is ran. I went ahead and attached it to the query in the beforeFind() event, which seems to work.
See example below:
<?php
class ImportSettingsListener implements Cake\Event\EventListenerInterface
{
public function implementedEvents(): array
{
return [
'Model.beforeFind' => 'generateQuery',
];
}
public function generateQuery(Event $event, Query $query, ArrayObject $options, bool $primary): void
{
$query->formatResults(function (CollectionInterface $results) {
return $results->map(function ($setting) {
// Re-format $setting here
return $setting;
});
});
}
}
I have two Laravel sites, a client and a server. The client connects to the server, which (among other things) provides a RESTful api for a database. While this setup may seem a bit convoluted and superfluous, it is necessary for the use-case.
How can I set up the client so that it uses the server's API to interact with resources?
For instance, the sever provides organisation models at an /organisations/{id} end point. If I wanted to display them all on the client, and provide a form for updating each organisation, is there a Laravel-esque way of doing this? Or is it something that'll end up being hand-rolled and hacky?
You have to use the http-client. Laravel provides already one, based on Guzzle.
So, in order to update something You have to create PUT route on Your Server and then just call it from Client like this:
$response = Http::put("https://YOUR.SERVER/organisations/$id", [
'name' => 'Steve',
'role' => 'Network Administrator',
]);
More info, You will find in Laravel Documentation
if you want to use the Laravel as a frontend client to consume a remote API it easy, a part the fact that you can consider the use of vue.js as your frontend in order to keep things simpler.
FRONTEND
Create the Laravel project as usual, the only difference is that you don't need to set up the database and migrations
You are going to use the normal MVC pattern here, so in your Frontend FlightController class, you will fetch the data like this.
getFlights()
{
$response = Http::get(env('API_URL') . '/flights')->json();
}
we can optionally install the package spatie/data-transfer-object
in order to convert your json data to DTO object like this:
getFlights()
{
$flights_data = Http::get(env('API_URL') . '/flights')->json();
$filghts = [];
foreach($flight_data as data)
$filghts->add(new \App\Models\Dto\FlightDto($data));
return view('flights.search-result', compact('filghts'));
}
the DTO class looks like this:
use Spatie\DataTransferObject\DataTransferObject;
class FlightDto extends DataTransferObject
{
/** #var integer|null */
public $id;
/** #var string|null */
public $flight_number;
}
The authentication is little bit tricky, refer to this question to see how you can create custom user provider.
BACKEND
Create a Laravel project, set up database and all your migrations
place all your routes in the default location api.php
Route::get('/trips', 'ApiTripsController#getFlights');
3- in the FlightsController do the following
public getFlights
{
return Flight::all() // where the Flight class is the Eloquent model
}
do not forget to provide a security layer to protect ressources from the server side
I am work with Laravel 5 and have implemented a working Restful API. I am trying to allow posts to be created by passing in an array like this...
post
title
excerpt
content
image
post
title
excerpt
content
image
The API currently works great for posting one individual post but how can I handle the array of posts instead?
Does anyone have an example I can see?
If you are using the Resource Controller, you should have a PostsController with a method store(). I am assuming your request payload is some JSON like this:
{"posts": [{"title":"foo"},{"title":"bar"}, …]}
So you need to json_decode the input. Then pass it to your database:
public function store()
{
$posts = json_decode($request->input('posts', ''));
if (is_array($posts)) {
DB::table('posts')->insert($posts);
}
}
There is probably some plugin or middleware or whatever to automatically decode the JSON payload. But apart from that, there is nothing special about doing what you ask for.
If you don't want to use the store() method (because it already stores a single Post or something) you can just add another route for your multiple Posts.
did you try to send JSON in the body? Here is a link with an example
Request body could look like the following:
{
"parameters":[
{
"value":{
"array":{
"elements":[
{
"string":{
"value":"value1"
}
},
{
"string":{
"value":"value2"
}
},
{
"string":{
"value":"value3"
}
}
]
}
},
"type":"Array/string",
"name":"arrayvariable"
}
]
}
This will convert the array every time you get it from the DB and every time you save it to the DB.
And here is an example using laravel casting link
Use attribute casting. Open your IdModel model and add this:
protected $casts = [
'id' => 'array' ];
I have an API to consume for a service that provides finance quotations on used cars. My app is written in PHP and I have Guzzle 5 added via Composer.
I have used other APIs previously that have take XML or just an Array of POST parameters to send, but this one is more complex.
This API uses DTO objects and the documentation says this:
relies heavily on DTOs to carry data between client and server. The following
sections detail the DTOs. Each web service will serialise and transfer them in their own
formats/methods. It is the responsibility of the client application to correctly construct requests and
parse responses. It is suggested that object serialization and deserialization be used for easier usage.
So I have no idea how to achieve this with Guzzle. Some of the enumeration types are things such as "RequestAssetMotorVehicle". Would you use StdClass or Arrays doing this in PHP? Or classes? How would I serialise it?
Guzzle Docs
Without the API's documentation this is difficult to express. But I'll try. We'll use a generic JSON based REST API
DTO standards are usually per company and sometimes per application. In short: A DTO is a serialized object.
Let's say this is a POST request ( we're creating a new user)
{
'name':'john',
'foo':'bar',
'site':'stackoverflow.com'
}
That JSON is a DTO. Now let's do a GET
{
'error':false,
'results':2,
'data': [{'name':'john','foo':'bar','site':'stackoverflow.com'},
{'name':'mark','foo':'bar','site':'notstackoverflow.com'}]
}
the array of 'data' is an array of DTO.
So what the dox are telling you is that you need to familiarize your application with the API by creating a layer for that data to pass through to be formed into objects on your side and the same layer should take an object and turn it into a DTO. In some cases you can just handle the responses from API's with simple code, however in a situation were the GET request would return more than 10 results you are going to want to parse it with some class. Essentially creating an ORM for DTOs.
As far as guzzle goes: set the body to what ever the results of pushing the data through the layer.
public function createUserWithSomeApi()
{
$g= new \Guzzle\Client();
$response = $g->post('http://api.some.place/v1/users', [
'body' => (new strangeApiDtoParser)->prepare($new_user_data)
]);
return ApiDtoParser::fromDTO($response->getBody());
}
And receive
public function getUsersFromSomeApi()
{
$g= new \Guzzle\Client();
$response = $g->get('http://api.some.place/v1/users', [
'query' => ['foo' => 'bar']
]);
return ApiDtoParser::fromDTO($response->getBody());
}
Now your parser:
class ApiDtoParser
{
public static function fromDto($raw)
{
$returnArray=[];
$decoded =json_decode($data,true);
foreach($decoded as $one){
$obj = new DtoObj;
foreach ($one as $key => $value) {
$meth = "set". ucfirst(strtolower($key));
$obj->{$meth}($var);
}
$returnArray[]=$obj;
}
return $returnArray;
}
}
Judging by the context of you excerpt, You will need to create a request based parser though
I'm looking to use Lithium framework to build my application config interface as I like its minimal approach and the document-store (i.e. Mongodb) centric model.
However, (and I know its not quite released yet), there is little-to-no information, tutorials or examples out there to move you on from the simple blog tutorial.
What I am trying to do now is build an app that will show me the collections I have in Mongodb, and then let me work with which ever collection I choose. I can't seem to figure out:
a) how would I build a model that enumerates the collections - preferably according to my internal naming scheme,
b) how do I break the convention model so I can specify the name of the collection to use?
I think there are two things i'm struggling with to answer these two questions - perhaps a fundamental misunderstanding of how to move a model in MVC beyond the simple collection-model-controller-view examples, and secondly, the actual process of telling the mongo datasource what collection to use.
any pointers or examples, gratefully received.
Chris
update::
So I figured out how to set the collection - for reference you can set source in the $_meta array like this:
protected $_meta = array(
'source' => '<<collectionName>>'
);
still no idea how to use a Model that will list me all the collections I have in my DB though. Any ideas how to do that from a philosophical and also technological manner?
further update::
so I have got a bit further thanks to the comments below. At least I might now be able to re-phrase the question a bit. I can define my model something like this:
<?php
namespace app\models;
use lithium\data\Model;
class Posts extends \lithium\data\Model{
protected $_meta = array('source' => false);
public function testcolls(){
return (self::connection()->sources());
}
}
?>
then in my view I can use:
<?php foreach ($post->testcolls() as $coll): ?>
<h2><?=$coll ?></h2>
<?php endforeach; ?>
that works - however, what I really want to do is not create a 'testcolls' method in my Model but as Medhi suggested below, I need to override the find method. I can't seem to figure out how to do that and what it would need to return. The docs are not too clear on this.
final update
based on the comment below and a bit of experimentation, I came up with the following that works for being able to call find with a collection as a parameter.
model:
class Dataqueues extends \lithium\data\Model{
protected $_meta = array('source' => false);
public static function find($filter, array $options = array()) {
if (isset($options['collection'])){
self::meta('source', $options['collection']);
}
return parent::find('all',$options);
}
}
controller:
class DataqueuesController extends \lithium\action\Controller {
public function index() {
$dataqueues = Dataqueues::find('all',array('limit'=>20,'collection'=>'W501'));
return compact('dataqueues');
}
}
getting a model that returns a list of collections was also pretty simple in the end:
class Collections extends \lithium\data\Model{
protected $_meta = array('source' => false);
public static function find($filter, array $options = array()) {
return self::connection()->sources();
}
}
note that the controller won't support options or filters.
Nothing holds you from having a Collections Model, where you set $_meta['source'] = false to prevent Lithium from looking for a Collection in your database named collections.
In this model, you can call YourModel::connection()->sources() to list all your Mongo Collections.
Docs for sources(): http://li3.me/docs/lithium/data/source/MongoDb::sources(). Basically it calls listCollections() on a MongoDB instance http://php.net/manual/en/mongodb.listcollections.php
You can override your Model::find() method to return the list of collections, instead the list of documents, or pass the collection as a param Collections::find('all', array('conditions' => ..., 'collection' => 'foo'))... or wathever you want :-)
Lithium is designed to don't force that much on you !
First of all, Lithium follows the convention over configuration approach.
What this means:
Configuration: 'source' => '<< collectionName >>'
Convention: Name your model and your collection the same thing, the framework handles the rest.
IE: A "People" collection will have a "People" model
Second, connect to your database:
Configure your connections.php file in app\bootstrap\connections.php. I know I said convention over configuration, but you still need to let the framework know where the database is and what the login info is. For details look at the http://li3.me/docs/manual/quickstart. Here is the relevant code:
// MongoDB Connection
Connections::add('default', array('type' => 'MongoDb', 'database' => 'blog', 'host' => 'localhost'));
Third, get data
Create a model, matching your collection name, and then in your controller, add this line:
$model = Models::all();
where model is the singular name for what you are storing in your collection, and Models is the name of your model. That is it.
If you put a break point after this line, you will see your Models collection. For more information, see http://li3.me/docs/manual/working-with-data/using-models.wiki
Finally, to pass it to your view, simply put this line of code at the end of your controller:
return compact('model', 'model2', 'model3');
where model would be what you just pulled in the third step. The models 2 and 3 that I tacked on is how you would pass any other collections you pulled.
In your view, you would just reference $model to and assume that the relevant fields are there. You don't have to worry about putting getters or setters or anything else like that.
For example: if you want to show the data in $model:
foreach ($model as $aModel) {
echo $aModel;
}
See Accessing View Variables in: http://li3.me/docs/manual/handling-http-requests/views.wiki
Hope this helps.