Shopify API in PHP using GuzzleHttp array of objects - laravel

I am currently attempting to update a bunch of products via the Shopify API, however, when I am sending the request, the product is created, however, it appears to be ignoring things where it is an array or arrays (for example, images or variants).
This is my Shopify Helper class that I am using for all requests.
class Shopify {
protected $api_key;
protected $password;
protected $url;
protected $host;
protected $secret;
protected $client;
public function __construct() {
$this->api_key = env('SHOPIFY_API_KEY');
$this->password = env('SHOPIFY_API_PASSWORD');
$this->secret = env('SHOPIFY_API_SHARED_SECRET');
$this->host = env('SHOPIFY_API_HOST');
$this->url = "https://{$this->api_key}:{$this->password}#{$this->host}";
$this->client = new Client();
}
public function __call($method, $args)
{
$method = strtoupper($method);
$allowedMethods = ['POST','GET','PUT','DELETE'];
if(!in_array($method,$allowedMethods)){
throw new InvalidMethodRequestException();
}
return $this->request($method,trim($args[0]),$args[1] ?? []);
}
protected function request(string $method, string $uri, array $payload)
{
$response = $this->client->request(
$method,
"{$this->url}{$uri}",
[
'form_params' => $payload
]
);
return json_decode($response->getBody());
}
}
Here is an example of me using this client to create a product with a variant:
$shopify = new Shopify();
$result = $shopify->post('/admin/api/2020-10/products.json', [
'product' => [
'title' => $product->title,
'body_html' => $product->body_text,
"variants" => [
[ "sku" => $product->sku, "price" => 20.00 ]
]
]
]);
As I mentioned above, the product is created in Shopify, but is missing anything where the data is an array of arrays. Could this be todo with the way GuzzleHttp encodes the data? How can get this data in a format that Shopify needs?

Your code looks good. The variants output maybe need some conversion to json format.
As you can see here, the expected post request:
"variants": [
{
"option1": "First",
"price": "10.00",
"sku": "123"
},
I would try to dump the post variable before the request and see what is wrong.
I made some successfull code with laravel in the past using Facades, and worked. But it took me some time to make it as shopify needs.
PS: I'm using tokens here https://www.shopify.com/partners/blog/17056443-how-to-generate-a-shopify-api-token
See:
//my endpoint, in your case '/admin/api/2020-10/products.json
$endpoint = config('endpoint');
//registered as a token.
$token = config('token');
$postFields = 'product' => [
'title' => $product->title,
'body_html' => $product->body_text,
"variants" => [
[ "sku" => $product->sku, "price" => 20.00 ]
]
];
//in case of any problems, you can uncomment this line and inspect your request.
//json_encode will help with this
//dd(json_encode($postFields));
return Http::withHeaders([
"content-type" => "application/json",
"Authorization" => "Bearer " . $token
])->post($endpoint, $postFields)->json();

Related

Path of the file stored in s3 does not match with provided - Using Laravel

I'm building a service to upload images with laravel and stored in a aws s3 bucket, this is the function responsible for store image.
public function fromUrl(Request $request)
{
$validator = Validator::make($request->all(), [
'files' => 'required|array|min:1',
'files.*' => 'string',
]);
if (!$validator->fails()) {
$paths = [];
foreach ($validator->validate()['files'] as $file) {
$url = config('services.s3.host') . Storage::disk('s3')->put('images/public', file_get_contents($file), 'public');
array_push($paths, $url);
}
return $paths;
} else {
throw new ValidateException([
'message' => $validator->errors()->toArray(),
'rules' => $validator->failed()
]);
}
}
The request body looks like this.
{
"files": [
"https://image-url-1",
"https://image-url-2"
]
}
I expect that the path returned when saving the image is something like this.
[
"https://my-bucket-url/images/public/random-name-for-image1",
"https://my-bucket-url/images/public/random-name-for-image2"
]
but instead I'm getting the following.
[
"https://my-bucket-url/1",
"https://my-bucket-url/1"
]
You are misusing put in your example.
Firstly the first parameter is the path plus filename and you have no filename random logic. The third parameter is options array.
$randomFileName = uniqid(rand(), true);
$path = 'images/public/' . $randomFileName;
Storage::disk('s3')->put($path, file_get_contents($file));
This code will save an element at images/public/$randomFileName. To return the proper path you can use the url() method.
$url = Storage::disk('s3')->url($path);
array_push($paths, $url);

Passing return data to another function in same controller laravel

Try to connect to external API.
In first function, I already received token with authentication.
To send POST request, I need to put xtoken that I received from first function as second function.
I don't know how to send value to second function (registerUser)
Route::get('/connect', 'Guzzlecontroller#registerUser')->name('registeruser');
this is my route file
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Client;
use App\Http\Controllers\Auth\RegisterController;
class GuzzleController extends Controller
{
public function Gettoken()
{
$client = new \GuzzleHttp\Client();
$request = $client->get(
'http://api01.oriental-game.com:8085/token',
[
'headers' => [
'X-Operator' => 'mog189b',
'X-key' => 'sQxAVNaEMe0TCHhU',
]
]
);
$response = $request->getBody();
$tokenReturn = json_decode($response, true);
$xtoken = array("x-token:" . $tokenReturn['data']['token'],);
$this->registerUser($xtoken);
}
public function registerUser($xtoken)
{
$client = new \GuzzleHttp\Client();
$url = "http://api01.oriental-game.com:8085/register";
$request = $client->post($url, [
'headers' => $xtoken,
'body' => [
'username' => 'test1',
'country' => 'Korea',
'fullname' => 'test user1',
'language' => 'kr',
'email' => 'testuser1#test.com',
]
]);
$response = $request->send();
dd($response);
}
}
Too few arguments to function App\Http\Controllers\GuzzleController::registerUser(), 0 passed and exactly 1 expected
this is error I am getting.
please help me to how to send $xtoken value to registerUser function
The problem is Laravel is calling registerUser directly instead of going through getToken. So the token is never retrieved and passed to the register action.
Instead of calling registerUser() from Gettoken(). Have Gettoken() return the token and call it from registerUser()
public function Gettoken()
{
...
return $xtoken;
}
public function registerUser()
{
$xtoken = $this->Gettoken();
...
}

How to post data in from back-end testcase to controller

I am new to php and I am currently trying to make a testcase for an add function I wrote for adding records in the table "project_point" of my database. In this testcase I want to post some test data to that add function and check if the data is set correctly.
Project Point Add function
public function addProjectPoint (Request $request) {
$point = new ProjectPoint();
$location = new Point($request->markerLat, $request->markerLong);
$point->project_id = $request->project_id;
$point->location = $location;
$point->area = $request->area;
$point->name = $request->name;
$point->information = $request->information;
$point->category = $request->category;
$point->save();
}
My test case
public function testCreateProjectPoint()
{
$this->post('admin/projectpoint/create', [
'project_id' => 1,
'markerLat' => 5.287020206451416,
'markerLong' => 51.68828138589033,
'area' => null,
'name' => 'TestCaseProjectPoint',
'information' => 'This is a automated test ProjectPoint, please delete this point!',
'category' => 'bezienswaardigheid'
]);
$this->assertDatabaseHas('interest_point', [
'project_id' => 1,
'location' => new Point(5.287020206451416, 51.68828138589033),
'area' => null,
'name' => 'TestCaseProjectPoint',
'information' => 'This is a automated test ProjectPoint, please delete this point!',
'category' => 'bezienswaardigheid'
]);
/*
$test = factory(ProjectPoint::class)->create();
$this->post('admin/projectpoint/create', $test);
$this->assertDatabaseHas('project_point', $test);
*/
}
ProjectPoint model
class ProjectPoint extends Model
{
use SpatialTrait;
protected $table = 'interest_point';
protected $fillable = ['project_id', 'name', 'information', 'category' ];
protected $spatialFields = [
'location',
'area'
];
public $timestamps = false;
public function project()
{
return $this->belongsTo('App\Models\Project', 'project_id');
}
}
The output of the test is:
Failed asserting that a row in the table [interest_point] matches the attributes {
"project_id": 1,
"location": {
"type": "Point",
"coordinates": [
51.68828138589033,
5.287020206451416
]
},
"area": null,
"name": "TestCaseProjectPoint",
"information": "This is a automated test ProjectPoint, please delete this point!",
"category": "bezienswaardigheid"
}.
But I expect to see the test case succeed and when checking the database no records have been added to the database
Have you tried still putting location and area in your fillables?

Laravel POST request to API answer

I have got a Laravel Application which should send a POST request with parameters to the WebUntis API and the response should be a session id. I can send a POST request and I get a answer but I do not get the session key. I have tested the code in Postman and there it works.
route/api.php
Route::get('auth', 'UntisController#auth');
UntisController.php
class UntisController extends Controller
{
public function auth()
{
$client = new \GuzzleHttp\Client();
$req = $client->post( 'https://asopo.webuntis.com/WebUntis/jsonrpc.do?school=htblva_villach',[
"id" => 8294829,
"method" => "authenticate",
"params" => [
"user" => "USERNAME",
"password" => "PASSWORD",
"client" => "web"
],
"jsonrpc" => "2.0"
],
['Content-Type' => 'application/json'
]);
dd($req);
}
}
the response I get with POSTMAN, the respones i want
my Postman request
The response I get from my code
I have tried it already with
dd($req->getBody());
but I do not get the sessionid i want.
You can try this method.(Send post request with api in laravel)
public function auth(Request $request)
{
$token = session('token'); //if token needed you can take from session else you can remove token
$body = [
"id" => 8294829,
"method" => "authenticate",
"params" => [
"user" => "USERNAME",
"password" => "PASSWORD",
"client" => "web"
],
"jsonrpc" => "2.0"
];
$request =
Request::create('https://asopo.webuntis.com/WebUntis/jsonrpc.do?
school=htblva_villach') , 'POST', $body);
$request->headers->set('Accept', 'application/json');
$request->headers->set('Authorization', 'Bearer ' . $token); // if not required token you can comment this line
$res = app()->handle($request);
$req = json_decode($res->getContent()); //convert to json object
dd($req);
}

Better way for testing validation errors

I'm testing a form where user must introduce some text between let's say 100 and 500 characters.
I use to emulate the user input:
$this->actingAs($user)
->visit('myweb/create')
->type($this->faker->text(1000),'description')
->press('Save')
->see('greater than');
Here I'm looking for the greater than piece of text in the response... It depends on the translation specified for that validation error.
How could do the same test without having to depend on the text of the validation error and do it depending only on the error itself?
Controller:
public function store(Request $request)
{
$success = doStuff($request);
if ($success){
Flash::success('Created');
} else {
Flash::error('Fail');
}
return Redirect::back():
}
dd(Session::all()):
`array:3 [
"_token" => "ONoTlU2w7Ii2Npbr27dH5WSXolw6qpQncavQn72e"
"_sf2_meta" => array:3 [
"u" => 1453141086
"c" => 1453141086
"l" => "0"
]
"flash" => array:2 [
"old" => []
"new" => []
]
]
you can do it like so -
$this->assertSessionHas('flash_notification.level', 'danger'); if you are looking for a particular error or success key.
or use
$this->assertSessionHasErrors();
I think there is more clear way to get an exact error message from session.
/** #var ViewErrorBag $errors */
$errors = request()->session()->get('errors');
/** #var array $messages */
$messages = $errors->getBag('default')->getMessages();
$emailErrorMessage = array_shift($messages['email']);
$this->assertEquals('Already in use', $emailErrorMessage);
Pre-requirements: code was tested on Laravel Framework 5.5.14
get the MessageBag object from from session erros and get all the validation error names using $errors->get('name')
$errors = session('errors');
$this->assertSessionHasErrors();
$this->assertEquals($errors->get('name')[0],"The title field is required.");
This works for Laravel 5 +
Your test doesn't have a post call. Here is an example using Jeffery Way's flash package
Controller:
public function store(Request $request, Post $post)
{
$post->fill($request->all());
$post->user_id = $request->user()->id;
$created = false;
try {
$created = $post->save();
} catch (ValidationException $e) {
flash()->error($e->getErrors()->all());
}
if ($created) {
flash()->success('New post has been created.');
}
return back();
}
Test:
public function testStoreSuccess()
{
$data = [
'title' => 'A dog is fit',
'status' => 'active',
'excerpt' => 'Farm dog',
'content' => 'blah blah blah',
];
$this->call('POST', 'post', $data);
$this->assertTrue(Post::where($data)->exists());
$this->assertResponseStatus(302);
$this->assertSessionHas('flash_notification.level', 'success');
$this->assertSessionHas('flash_notification.message', 'New post has been created.');
}
try to split your tests into units, say if you testing a controller function
you may catch valication exception, like so:
} catch (ValidationException $ex) {
if it was generated manually, this is how it should be generated:
throw ValidationException::withMessages([
'abc' => ['my message'],
])->status(400);
you can assert it liks so
$this->assertSame('my message', $ex->errors()['abc'][0]);
if you cannot catch it, but prefer testing routs like so:
$response = $this->json('POST', route('user-post'), [
'name' => $faker->name,
'email' => $faker->email,
]);
then you use $response to assert that the validation has happened, like so
$this->assertSame($response->errors->{'name'}[0], 'The name field is required.');
PS
in the example I used
$faker = \Faker\Factory::create();
ValidationException is used liks this
use Illuminate\Validation\ValidationException;
just remind you that you don't have to generate exceptions manually, use validate method for common cases:
$request->validate(['name' => [
'required',
],
]);
my current laravel version is 5.7

Resources