I'm currently writing some tests for my API, and I'm curious to know if there is a better way to deal with this as I feel like this is the "hacky" way of doing things.
Code example below:
public function testListingOfAllUsers()
{
$users = $this->createUsers();
$client = $this->createClient();
$client->request("GET", "/users/");
$response = $client->getResponse();
$content = $response->getContent();
$decodedContent = json_decode($content);
$this->assertTrue($response->isOk());
$this->assertInternalType("array", $decodedContent->data);
$this->assertCount(count($users), $decodedContent->data);
foreach ($decodedContent->data as $data) {
$this->assertObjectHasAttribute("attributes", $data);
$this->assertEquals("users", $data->type);
}
}
I'm wondering if there's something better I can do to test my API matches the JSON API specification. Enlighten me! I'm pretty certain PHPUnit isn't my answer here.
First of all, I don't believe that programatically asserting a certain JSON structure as you're doing right now is bad practice per se. However, I do agree that it might get cumbersome at some point and could be solved more efficiently.
I was having the same issue a while ago and ended up writing a new Composer package (helmich/phpunit-json-assert, which is available as open source) that uses JSON schemata and JSONPath expressions for verifying the structure of a given JSON document.
Using a JSON schema, your example test case could be written as follows:
public function testListingOfAllUsers()
{
$users = $this->createUsers();
$client = $this->createClient();
$client->request("GET", "/users/");
$response = $client->getResponse();
$content = $response->getContent();
$decodedContent = json_decode($content);
$this->assertTrue($response->isOk());
$this->assertJsonDocumentMatchesSchema($decodedContent, [
'type' => 'array',
'items' => [
'type' => 'object',
'required' => ['attributes', 'type'],
'properties' => [
'attributes' => ['type' => 'object'],
'type' => ['type' => 'string', 'enum' => ['user']]
]
]
]);
}
Although a little more verbose (with regards to lines-of-code), I've come to appreciate JSON schemas for this use case, as it's a widely adopted standard and (imho) easier to read that a wall of assert* statements. You could also extract the schema definitions from your unit tests into separate files and do other stuff with them; for example auto-generating documentation (Swagger also uses a subset of JSON schema) or run-time validation.
Related
I'm using Codeigniter 4.
And inserting new data like this,
$data = [
'username' => 'darth',
'email' => 'd.vader#theempire.com'
];
$userModel->save($data);
Which is mentioned here: CodeIgniter’s Model reference
It's doing the insertion.
But I haven't found any reference about to get the inserted id after insertion.
Please help! Thanks in advance.
This also works.
$user= new UserModel();
$data = [
'username' => 'darth',
'email' => 'd.vader#theempire.com'
];
$user->insert($data);
$user_id = $user->getInsertID();
I got a simple solution after researching on the core of the CI 4 framework.
$db = db_connect('default');
$builder = $db->table('myTable');
$data = [
'username' => 'darth',
'email' => 'd.vader#theempire.com'
];
$builder->insert($data);
echo $db->insertID();
Hope they'll add a clear description on the docs soon.
There are three way to get the ID in ci4:
$db = \Config\Database::connect();
$workModel = model('App\Models\WorkModel', true, $db);
$id = $workModel->insert($data);
echo $id;
echo '<br/>';
echo $workModel->insertID();
echo '<br/>';
echo $db->insertID();
In fact, what you did is correct.
You did it in the best and easiest way and following the Codeigniter 4 Model usage guide.
You just missed: $id = $userModel->insertID;
Complete code using your example:
$data = [
'username' => 'darth',
'email' => 'd.vader#theempire.com'
];
$userModel->save($data);
$id = $userModel->insertID;
That's it. You don't need all this code from the examples above nor calling database service or db builder if you're using codeigniter's models.
Tested on CodeIgniter 4.1.1 on 3/19/2021
To overcome this, I modified system/Model.php in the save() method---
$response = $this->insert($data, false);
// add after the insert() call
$this->primaryKey = $this->db->insertID();
Now, in your models, you can just reference "$this->primaryKey" and it will give you the needed info, while maintaining the data modeling functionality.
I'm going to submit this over to the CI developers, hopefully it will be added in.
For CI4
$settings = new SettingsModel();
$settingsData = $settings->find(1);
<?php namespace App\Models;
use App\Models\BaseModel;
class SettingsModel extends BaseModel
{
protected $table = 'users';
protected $primaryKey = 'id';
}
$settings->find(1); will return a single row. it will find the value provided as the $primaryKey.
hi guys in my case i use ci model to save data and my code is :
$x=new X();
$is_insert= $x->save(['name'=>'test','type'=>'ss']);
if($is_insert)
$inserted_id=$x->getInsertID()
I'm using mysql for my database then I ran this inside my seeder
$university = $this->db->table('universities')->insert([
'name' => 'Harvard University'
]);
$faculty = $this->db->table('faculties')->insert([
'name' => 'Arts & Sciences',
'university' => $university->resultID
]);
Look at code line 6
$university->resultID
variable $university here is type object of CodeIgniter\Database\MySQLi\Result class
Corect me if I'm wrong or any room for improvements
I had the same problem but, unfortunately, the CI4 documentation doesn't help much. The solution using a builder woks, but it's a workaround the data modeling. I believe you want a pure model solution, otherwise you wouldn't be asking.
$data = [
'username' => 'darth',
'email' => 'd.vader#theempire.com'
];
$id = $userModel->save($data);
Trying everything I could think of I decided to store the result of the save method to see if returned a boolean value to indicate if the saving was sucessful. Inspecting the variable I realized it returns exactly what I wanted: the lost insertID.
I believe CodeIgniter 4 is quite an easy and capable framework that does a decent job in shared hosts where other frameworks can be a little demanding if you're learning but lacks the same fantastic documentation and examples of CI3. Hopefully, that's only temporary.
By the way, you code works only if you are using the $userModel outside the model itself, for example, from a Controller. You need to create a model object like:
$userModel = New WhateverNameModel();
$data = [any data];
$userModel->save($data);
Alternatively, if you are programming a method inside the model itself (my favorite way), you should write
$this->save($data);
I am using the Firebase PHP Admin SDK: https://firebase-php.readthedocs.io/en/stable/realtime-database.html#update-specific-fields
Here is the example it gives to update specific fields:
$uid = 'some-user-id';
$postData = [
'title' => 'My awesome post title',
'body' => 'This text should be longer',
];
// Create a key for a new post
$newPostKey = $db->getReference('posts')->push()->getKey();
$updates = [
'posts/'.$newPostKey => $postData,
'user-posts/'.$uid.'/'.$newPostKey => $postData,
];
$db->getReference() // this is the root reference
->update($updates);
From that, I created a users class and in that I have an update function. Like so:
public function update() {
$data = array('users' => array('1' => 'David'));
$this->database->getReference()->update($data);
return true;
}
In my database I have this structure:
Now if I run that function $users->update();
It removes the other child and only leaves David. Like so:
How can I update only a specific value of a specified key without it overriding the other data?
There's nothing specific to PHP here. That's the way Realtime Database update operations work. If you want a minimal update, you have to target the deepest key that you want to update. In your case, since you're storing an array type object, the keys are the number indexes of the array items you've written. If you want to modify one of them, you need to build a reference that includes the child number you want update. In that case, none of the sibling values will be touched.
Try this instead:
$this->database->getReference('users')->update(array('1' => 'David'));
Notice here that the update is rooted at "users", and you're updating just the immediate child "1" of that.
The example on docs is a little bit hard to grasp as a beginner. I have made it simpler for you to understand.
Once you get the newPostKey, prepare the url for child and run the code. It will only change the specific fields.
$ref = 'users/' . $newPostKey;
$updates = [
'title' => 'Updated title',
'body' => 'Updated body text',
];
$set = $db->getReference($ref)->update($updates);
I did read the elasticsearch documentation but it lacks examples, for me.
I have put some documents in the es-engine, they contain the field "text" and "title".
Now I want to boost up the hits in the field "title". I'm using the php-api.
I tried this one:
$params_ci['index'] = 'all';
$params_ci['type'] = 'all';
$params_ci['body']['query']['query_string']['query'] = $query;
$params_ci['body']['function_score']['functions']['field_value_factor'] = array('field' => 'title',
'factor' => 1.2)
$client = ElasticClientFactory::build();
$client->search($params_ci)
But I get an error. Without the "function_score" it works.
... Parse Failure [No parser for element [function_score] ...
The biggest problem for me is how to translate the JSON in the documentation into the right place as arrays. I know JSON is like an array but often it fits not in my hierarchy of array?
Uses ElasticSearch Version: elasticsearch-1.1.1
The right synax is:
$params_ci['body']['query']['function_score']['functions']['field_value_factor'] = array('field' => 'title', 'factor' => 1.2)
I'm using Symfony2 and making a php curl request to an api. I'm looking to make sure the results are being returned in the correct form: as a json string (not a php object or something else). The returned json string is consumed by renderAPIResults() that uses twig to "render" a JsonResponse. Is this possible?
Can I "render" the response in renderAPIResults or do I need to return a JsonResponse and have the twig template render it, having been called from apiAction()?
Is there any built in Symfony2 functionality wherein if one names a template results.json.twig vs. results.html.twig, or is this naming convention is just idiomatic?
I have been able to get the results into results.html.twig by rendering a Response() in the renderAPIResults() method but I have only been able to console.log() the results from a .js file when returning the results as a JsonResponse(). I have not been able to parse these results in any shape or form in the results.json.twig template when trying to somehow 'render' a JsonResponse.
//DefaultController.php
public function curlRestRequest($apiQueryString, $jsonDecode = FALSE, array $post_data = null, $service_url = null){
if(!$service_url) {
$service_url = 'http://website.com/rest';
}
$curl = curl_init($service_url.$apiQueryString);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $post_data);
$curl_response = curl_exec($curl);
if ($curl_response === false) {
$info = curl_getinfo($curl);
curl_close($curl);
die('error occured during curl exec. Additioanl info: ' . var_export($info));
}
curl_close($curl);
if($jsonDecode)
{
$curl_response = json_decode($curl_response);
}
return $curl_response;
}
public function renderAPIResults($data, $asObject = FALSE, $template)
{
if(!$template) {
throw new Exception('Must provide a template name before rendering!');
}
if($asObject != TRUE) {
// we want to cast to array since specified as OBJECT = FALSE, removing all instances of objects
$dataArray = json_decode(json_encode($data), true);
return $this->render($template, array('data' => $dataArray));
} else { // Is JSON Decoded, if it is an object
//just return json data, parse it in javascript instead of with template
$response = new JsonResponse();
$response->setData(array(
'data' => $data
));
return $response;
//return $this->renderView($template, array('data' => $data));
//or
//$content = $this->render($template, array('data' => $data));
//return new JsonResponse($content);
}
}
public function apiAction($type, $query){
$apiQueryString = '/search/' . $type . '?query=' . $query;
//make a curl request
$requestedResults = ($this->curlRestRequest($apiQueryString, TRUE));
//return option three: (dont use any response object, and just pass, just pass curl-results directly)
//note, currently, when set as true, its not rendered in the template, the template is not used
$view = $this->renderAPIResults($requestedResults, TRUE, 'ApiBundle:API:results.json.twig');
return $view;
}
This is the twig template:
//results.json.twig
{{ dump(data) }}
Basically, im asking:
How do I utilize JsonResponse() in the renderAPIResults() method to render the results.json.twig in that same method, returning this rendered template so the template itself can loop over the json results?
Can I use JsonResponse() to render a template this way? Or do I have to use only the Response() method when rendering?
If I can't use JsonResponse to render, can I parse the results directly in twig using its native language? i.e. {{ dump(results) }} or do I need to include javascript inside scripts tags and process these results first?
EDIT: I think i found a solution to the problem.
when you json_decode($string,TRUE), if its a deeply nested array, then the nested components don't end up as a perfect nested array as i assumed it would, only the top level ones do. Is this a natural side effect of json_decode, or bad json, or what am i doing wrong?
I think this post help shed some light on this. Accessing JSON array after json_decode/multidimensional array
So it appears the problem i was having was the JSON data wasnt completely clean.
i.e.
$requestedResults = ($this->curlRestRequest($apiQueryString, TRUE));
print_r($requestedResults);
//yields Array
(
[category] =>
[executionTime] => 759
[facets] =>
[resultCount] => 8
[searchCount] => 0
[searchInfo] =>
[searchResults] => Array
(
[0] => Array
(
[description] => Gives instructions for 3 different in-class games.
[taxonomyDataSet] => {"course":[],"topic":[],"unit":[],"lesson":[],"subject":[],"curriculum":{"curriculumName":["Common Core State Standards - Math","Common Core State Standards - Math"],"curriculumCode":["CCSS.M.8.G.C.9","CCSS.M.7.G.B.6"],"curriculumDesc":["Solve real-world and mathematical problems involving area, volume and surface area of two- and three-dimensional objects composed of triangles, quadrilaterals, polygons, cubes, and right prisms.","Know the formulas for the volumes of cones, cylinders, and spheres and use them to solve real-world and mathematical problems."]}}
[thumbnail] => slides/thumbnail.jpg
[title] => Game Suggestions for Volume
)
)
which the "curriculumCode" for e.g. wasnt accessible, or anything in the taxonomyDataSet wasnt accessible, via twig, and i had to massage the data a bit to clean it.
$requestedResults = ($this->curlRestRequest($apiQueryString, TRUE));
foreach ($requestedResults['searchResults'] as $resource) {
$title = $resource["title"];
$description = $resource["description"];
$thumbnailUrl = $resource["thumbnails"]['url'];
$taxonomyDataSet = json_decode($resource['taxonomyDataSet'],TRUE);
$standard = $taxonomyDataSet['curriculum']['curriculumCode'];
if(! file_exists($thumbnailUrl))
{
$thumbnailUrl = NULL;
}
$results[] = array($title,$description,$thumbnailUrl, $standard);
}
print_r($results);
//yields
Array
(
[0] => Array
(
[0] => Game Suggestions for Volume
[1] => Gives instructions for 3 different in-class games.
[2] =>
[3] => Array
(
[0] => CCSS.M.8.G.C.9
[1] => CCSS.M.7.G.B.6
)
))
Why Am i having to take this extra step, isn't there a better way to properly iterate over a JSON string so this doesnt happen?
I would MUCH prefer keeping it an object, then simple return it like this in the controller return $requestedResults->searchResults
where i can safely iterate it in TWIG without all the data massaging.
Edit, ok looking into this once again deeper, i believe that the company's API data is to blame, it looks like one particular portion of the results is double json_encoded, and this make sense why the data is not accessible until i massage it. is it me, or does it appear that this is whats happening?
I’m not sure if I understood the question, but let’s go:
In my opinion, you should process your API result before send it to the view, that means you should parse the JSON string into PHP objects, create an array of it, and then iterate on twig as normal.
So, focus your effort on develop a piece of code that turns your JSON string into an array of objects; and don’t pass logic to the view.
Consider using FOSRestBundle, it will handle your request and return correct JSON responses "automatically". It is simple to configure basic usage and then you have to only follow conventions to achieve simple and valid REST API.
You can use Guzzle as API client for retrieving data. You can write some client class and provide methods implementing some logic (method for each resource). Then in those methods you simply use, for example, $response = $guzzleClient->get($url, $options) and under $reponse->json() you have all the data which you can process and serve in your own API response.
You should register that API client as service and use it in own controllers to retrieve data, then process it and return expected result from controller's action. You can handle it then by FOSRestBundle+JMSSerializerBundle to make JSON response (you can return XML too, it's a matter of using allowed formats and requesting resource with format parameter provided - for example getting /api/users.xml would return XML file, while /api/users.json will result in JSON).
This is wide topic and it's nearly impossible to describe all the stuff you have to do in order to achieve what you want, but I believe it will help you to get right resolution.
Not a dictionary for language, but data. I.e Car1[Make="Honda",Model="Accord",Colour="Red"]
That's how I'd do it in Python, but by the look of things, it's harder to do on the web. I'm using PHP (not ASP).
Has anyone had any experience with writing this kind of thing on web? I'm open to JS etc if needs be. I've seen a couple of hacks for PHP, but would like to know anything more suited.
Dictionary from Python can be compared to associative arrays in (or, less probable, objects) PHP and simple objects (with similar notation as in Python, called JSON) in JavaScript, if you asked for that.
Data structures could be saved as an array;
$car1 = array( 'make' => 'Honda', 'model' => 'Accord', 'colour' => 'Red' );
Or you could make an object;
class cars{
private $make;
private $model;
private $colour;
public function __construct($make, $model, $colour)
{
$this->make = $make
$this->model = $model;
$this->colour = $colour;
}
}
$car1 = new cars('Honda', 'Accord', 'Red');
Or you save it an a database.
Or as an YAML and/or JSON document.
Or as an XML document.
All depending on what and how much data you have.
You could store it as a PHP array:
$car = array(
'make' => 'Honda',
'model' => 'Accord',
'colour' => 'Red'
);
That looks like a regular associative array to me