Not sure what is the correct way to display in a php page a Psr7 Guzzle Response.
Right now, I am doing:
use GuzzleHttp\Psr7\BufferStream;
use GuzzleHttp\Psr7\Response;
class Main extends \pla\igg\Main
{
function __construct()
{
$stream = new BufferStream();
$stream->write("Hello I am a buffer");
$response = new Response();
$response = $response->withBody($stream);
$response = $response->withStatus('201');
$response = $response->withHeader("Content-type", "text/plain");
$response = $response->withAddedHeader("IGG", "0.4.0");
//Outputing the response
http_response_code($response->getStatusCode());
foreach ($response->getHeaders() as $strName => $arrValue)
{
foreach ($arrValue as $strValue)
{
header("{$strName}:{$strValue}");
}
}
echo $response->getBody()->getContents();
}
}
Is there a more OOP way to display the response?
A more OOP way of doing the same thing is to create a Sender object that requires a ResponseInterface in its constructor. This class would be responsible for setting headers, clearing buffers and render the response:
use Psr\Http\Message\ResponseInterface;
class Sender
{
protected $response;
public function __construct(ResponseInterface $response)
{
$this->response = $response;
}
public function send(): void
{
$this->sendHeaders();
$this->sendContent();
$this->clearBuffers();
}
protected function sendHeaders(): void
{
$response = $this->response;
$headers = $response->getHeaders();
$version = $response->getProtocolVersion();
$status = $response->getStatusCode();
$reason = $response->getReasonPhrase();
$httpString = sprintf('HTTP/%s %s %s', $version, $status, $reason);
// custom headers
foreach ($headers as $key => $values) {
foreach ($values as $value) {
header($key.': '.$value, false);
}
}
// status
header($httpString, true, $status);
}
protected function sendContent()
{
echo (string) $this->response->getBody();
}
protected function clearBuffers()
{
if (function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
} elseif (PHP_SAPI !== 'cli') {
$this->closeOutputBuffers();
}
}
private function closeOutputBuffers()
{
if (ob_get_level()) {
ob_end_flush();
}
}
}
Use it like this:
$sender = new Sender($response);
$sender->send();
Better yet, you could inject the Sender into your app object and transform it in a class variable, so you'd call it like this:
function renderAllMyCoolStuff()
{
$this->sender->send();
}
I'll leave it as a reader's exercise to implement getters and setters for the Response object, plus a method to receive some content string and transform it into a Response object internally.
Guzzle is a library for doing HTTP calls inside your app, it has nothing to do with the end user communication.
If you need to send specific headers to your end user, just use http_response_code() (that you are already using), header() and echo. Or see the docs for your framework, if you use one (Symfony, Slim, whatever).
Related
I am fairly new to Laravel. This may have an obvious solution but I can't seem to find it so far. Therefore I am asking for help.
Question In Short:
I use Illuminate\Http\Request session ($request->session()) to store some data I get from BigCommerce API. But I can't get them when I need the data.
Context:
I am building a sample app/boilerplate app for BigCommerce platform using Laravel/React. I have built the app using official documentation, semi-official posts released by BigCommerce team and sample codebase provided by them as well.
App works fine with local credentials from a specific store because they are given as environment variables to the app. However I can't read store_hash (which is necessary to fetch data from BigCommerce) and access token. Both I have put in $request->session() object.
I will paste the AppController.php code below, also code is publicly available here:
In the makeBigCommerceAPIRequest method below (as you can see my debugging efforts :)) I can get $this->getAppClientId(), but I can't get anything from $request->session()->get('store_hash') or $request->session()->get('access_token') which returns from $this->getAccessToken($request).
I have tried putting store hash into a global variable, but it didn't work.
From everything I have experienced so far, $request is not working as expected.
Any help appriciated, thanks in advance.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use mysql_xdevapi\Exception;
use Oseintow\Bigcommerce\Bigcommerce;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use Bigcommerce\Api\Client as BigcommerceClient;
use Illuminate\Support\Facades\Storage;
use App\Config; //Database Connection
use Bigcommerce\Api\Connection;
class AppController extends Controller
{
protected $bigcommerce;
private $client_id;
private $client_secret;
private $access_token;
private $storehash;
private $redirect_uri;
public function __construct(Bigcommerce $bigcommerce)
{
$this->bigcommerce = $bigcommerce;
$this->client_id = \config('app.clientId');
$this->client_secret = \config('app.clientSecret');
$this->redirect_uri = \config('app.authCallback');
}
public function getAppClientId()
{
if (\config('app.appEnv') === 'local') {
return \config('app.localClientId');
} else {
return \config('app.clientId');
}
}
public function getAppSecret()
{
if (\config('app.appEnv') === 'local') {
return \config('app.localClientSecret');
} else {
return \config('app.clientSecret');
}
}
public function getAccessToken(Request $request)
{
if (\config('app.appEnv') === 'local') {
return \config('app.localAccessToken');
} else {
return $request->session()->get('access_token');
}
}
public function getStoreHash(Request $request)
{
if (\config('app.appEnv') === 'local') {
return \config('app.localStoreHash');
} else {
return $request->session()->get('store_hash');
}
}
public function error(Request $request)
{
$errorMessage = "Internal Application Error";
if ($request->session()->has('error_message')) {
$errorMessage = $request->session()->get('error_message');
}
echo '<h4>An issue has occurred:</h4> <p>' . $errorMessage . '</p> Go back to home';
}
public function load(Request $request)
{
$signedPayload = $request->get('signed_payload');
if (!empty($signedPayload)) {
echo "hello";
$verifiedSignedRequestData = $this->verifySignedRequest($signedPayload);
if ($verifiedSignedRequestData !== null) {
echo "positive return";
$request->session()->put('user_id', $verifiedSignedRequestData['user']['id']);
$request->session()->put('user_email', $verifiedSignedRequestData['user']['email']);
$request->session()->put('owner_id', $verifiedSignedRequestData['owner']['id']);
$request->session()->put('owner_email', $verifiedSignedRequestData['owner']['email']);
$request->session()->put('store_hash', $verifiedSignedRequestData['context']);
echo $request->session()->get('store_hash');
$this->storehash = $verifiedSignedRequestData['context'];
echo ' store hash is at the moment : ' . $this->storehash . ' .....';
} else {
return "The signed request from BigCommerce could not be validated.";
// return redirect()->action([AppController::class, 'error'])->with('error_message', 'The signed request from BigCommerce could not be validated.');
}
} else {
return "The signed request from BigCommerce was empty.";
// return redirect()->action([AppController::class, 'error'])->with('error_message', 'The signed request from BigCommerce was empty.');
}
return redirect(\config('app.appUrl'));
}
public function install(Request $request)
{
// Make sure all required query params have been passed
if (!$request->has('code') || !$request->has('scope') || !$request->has('context')) {
echo 'Not enough information was passed to install this app.';
// return redirect()->action('MainController#error')->with('error_message', 'Not enough information was passed to install this app.');
}
try {
$client = new Client();
$result = $client->request('POST', 'https://login.bigcommerce.com/oauth2/token', [
'json' => [
'client_id' => $this->client_id,
'client_secret' => $this->client_secret,
'redirect_uri' => $this->redirect_uri,
'grant_type' => 'authorization_code',
'code' => $request->input('code'),
'scope' => $request->input('scope'),
'context' => $request->input('context'),
]
]);
$statusCode = $result->getStatusCode();
$data = json_decode($result->getBody(), true);
if ($statusCode == 200) {
$request->session()->put('store_hash', $data['context']);
$request->session()->put('access_token', $data['access_token']);
$request->session()->put('user_id', $data['user']['id']);
$request->session()->put('user_email', $data['user']['email']);
// $configValue = Config::select('*')->where('storehash', $data['context'])->get()->toArray();
// if (count($configValue) != 0) {
// $id = $configValue[0]['id'];
// $configObj = Config::find($id);
// $configObj->access_token = $data['access_token'];
// $configObj->save();
// } else {
// $configObj = new Config;
// $configObj->email = $data['user']['email'];
// $configObj->storehash = $data['context'];
// $configObj->access_token = $data['access_token'];
// $configObj->save();
// }
// If the merchant installed the app via an external link, redirect back to the
// BC installation success page for this app
if ($request->has('external_install')) {
return redirect('https://login.bigcommerce.com/app/' . $this->getAppClientId() . '/install/succeeded');
}
}
return redirect(\config('app.appUrl'));
} catch (RequestException $e) {
$statusCode = $e->getResponse()->getStatusCode();
echo $statusCode;
$errorMessage = "An error occurred.";
if ($e->hasResponse()) {
if ($statusCode != 500) {
echo "some error other than 500";
// $errorMessage = Psr7\str($e->getResponse());
}
}
// If the merchant installed the app via an external link, redirect back to the
// BC installation failure page for this app
if ($request->has('external_install')) {
return redirect('https://login.bigcommerce.com/app/' . $this->getAppClientId() . '/install/failed');
} else {
echo "fail";
// return redirect()->action('MainController#error')->with('error_message', $errorMessage);
}
}
// return view('index');
}
public function verifySignedRequest($signedRequest)
{
list($encodedData, $encodedSignature) = explode('.', $signedRequest, 2);
// decode the data
$signature = base64_decode($encodedSignature);
$jsonStr = base64_decode($encodedData);
echo $jsonStr;
$data = json_decode($jsonStr, true);
// confirm the signature
$expectedSignature = hash_hmac('sha256', $jsonStr, $this->client_secret, $raw = false);
if (!hash_equals($expectedSignature, $signature)) {
error_log('Bad signed request from BigCommerce!');
return null;
}
return $data;
}
public function makeBigCommerceAPIRequest(Request $request, $endpoint)
{
echo ' ...... trying to make an apiRequest now : with storehash : ' . $this->storehash . ' .............';
echo '...........................................';
echo 'other variables at the moment :::: ............... client ID :' . $this->getAppClientId() . '...................... token : ' . $this->getAccessToken($request) . '...............';
$requestConfig = [
'headers' => [
'X-Auth-Client' => $this->getAppClientId(),
'X-Auth-Token' => $this->getAccessToken($request),
'Content-Type' => 'application/json',
]
];
if ($request->method() === 'PUT') {
$requestConfig['body'] = $request->getContent();
}
$client = new Client();
$result = $client->request($request->method(), 'https://api.bigcommerce.com/' . $this->storehash . '/' . $endpoint, $requestConfig);
return $result;
}
public function proxyBigCommerceAPIRequest(Request $request, $endpoint)
{
if (strrpos($endpoint, 'v2') !== false) {
// For v2 endpoints, add a .json to the end of each endpoint, to normalize against the v3 API standards
$endpoint .= '.json';
}
echo ' asadssada ...... trying to make an apiRequest now : with storehash : ' . $this->storehash . ' .............' . $request->session()->get('store_hash') . ' ............ ';
$result = $this->makeBigCommerceAPIRequest($request, $endpoint);
return response($result->getBody(), $result->getStatusCode())->header('Content-Type', 'application/json');
}
}
Thanks for the detailed info. Though it would help to annotate the debug lines with confirmation of what they output, I am making the assumption that you have narrowed the problem down to the session storage and retrieval lines.
$request->session()->put() and get() are the correct ways to access session.
I would therefore suggest investigating Session configuration: https://laravel.com/docs/8.x/session#configuration
If using file-based sessions, confirm that there are no permissions errors, perhaps. Alternatively try and different session storage mechanism.
I make an unary call from PHP code like that:
public function getByIDs(array $ids): array
{
$request = new GetByIDsRequest();
$request->setIDs($ids);
$grpcRequest = $this->client->GetByIDs($request, [], ['timeout' => $this->waitTimeout]);
$response = $this->callWithRetries($grpcRequest);
if (!$response->isOk()) {
$status = $response->getStatus();
throw new GrpcServerException(__FUNCTION__, $status->getDetails(), (int)$status->getCode());
}
... // business logic here
}
private function callWithRetries(\Grpc\UnaryCall $grpcRequest): GrpcUnaryCallResponse
{
$response = null;
for ($attempt = 0; $attempt <= self::$maxRetries; $attempt++) {
if ($attempt > 0) {
usleep(mt_rand(1, 5) * self::$waitBeforeRetry);
}
$response = $this->unaryCall($grpcRequest);
$status = $response->getStatus()->getCode();
if ($response->isOk() || !self::isRetryable($status)) {
return $response;
}
}
return $response;
}
protected function unaryCall(UnaryCall $call): GrpcUnaryCallResponse
{
[$response, $status] = $call->wait();
return new GrpcUnaryCallResponse(
$response,
new GrpcStatus($status)
);
}
Sometimes I get the LogicException with message "start_batch was called incorrectly". Any thoughts how to deal with that exception and what is the root cause?
After some investigation I found out that the problem occurs because of reusing the instance of \Grpc\UnaryCall in retries. You must not do that otherwise you get a core dump.
The reason we got the LogicException but not a core dump is using a patched gRPC extension.
Some more technical details you can find in this issue
i have problem with rendering template in ZF2, where template is in string in variable. There is simple example:
$template = "<div>easy</div>";
$view = new \Zend\View\Model\ViewModel();
$view->setTemplate($template);
$renderer = new \Zend\View\Renderer\PhpRenderer();
$html = $renderer->render($view);
This code fail on rendering, the renderer think that the template is a path to file. And iam reallz not sure how to tell rendere its a string.
Thx for your time and respond.
You have to extend the PhpRenderer class and override the render method, in such a way that will use the string in the $template as the actual template:
class MyPhpRenderer extends PhpRenderer {
public function render($nameOrModel, $values = null)
{
if ($nameOrModel instanceof Model) {
$model = $nameOrModel;
$nameOrModel = $model->getTemplate();
if (empty($nameOrModel)) {
throw new Exception\DomainException(sprintf(
'%s: received View Model argument, but template is empty',
__METHOD__
));
}
$options = $model->getOptions();
foreach ($options as $setting => $value) {
$method = 'set' . $setting;
if (method_exists($this, $method)) {
$this->$method($value);
}
unset($method, $setting, $value);
}
unset($options);
// Give view model awareness via ViewModel helper
$helper = $this->plugin('view_model');
$helper->setCurrent($model);
$values = $model->getVariables();
unset($model);
}
// find the script file name using the parent private method
$this->addTemplate($nameOrModel);
unset($nameOrModel); // remove $name from local scope
$this->__varsCache[] = $this->vars();
if (null !== $values) {
$this->setVars($values);
}
unset($values);
// extract all assigned vars (pre-escaped), but not 'this'.
// assigns to a double-underscored variable, to prevent naming collisions
$__vars = $this->vars()->getArrayCopy();
if (array_key_exists('this', $__vars)) {
unset($__vars['this']);
}
extract($__vars);
unset($__vars); // remove $__vars from local scope
while ($this->__template = array_pop($this->__templates)) {
$this->__file = $this->resolver($this->__template);
try {
if (!$this->__file) {
$this->__content = $this->__template; // this line does what you need
}else{
ob_start();
$includeReturn = include $this->__file;
$this->__content = ob_get_clean();
}
} catch (\Exception $ex) {
ob_end_clean();
throw $ex;
}
if ($includeReturn === false && empty($this->__content)) {
throw new Exception\UnexpectedValueException(sprintf(
'%s: Unable to render template "%s"; file include failed',
__METHOD__,
$this->__file
));
}
}
$this->setVars(array_pop($this->__varsCache));
if ($this->__filterChain instanceof FilterChain) {
return $this->__filterChain->filter($this->__content); // filter output
}
return $this->__content;
}
}
and then you code should look like:
$template = "<div>easy</div>";
$view = new \Zend\View\Model\ViewModel();
$view->setTemplate($template);
$renderer = new MyPhpRenderer();
$html = $renderer->render($view);
Try by replacing '\' with _ underscore as Zend_View_Renderer_PhpRenderer
Currently I doing a project by using hitbox.tv api plug in to my website. I would like to update my video title during when I'm broadcasting live stream. I using guzzle 6 to send http request to api end point, the following is my code
myGuzzle.php
<?php
namespace myGuzzle;
require __DIR__.'/../vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
class myGuzzle{
public $params;
public $method;
public $endpoint;
public $body;
public $status;
public $errorJson;
function response($params=[],$method,$endpoint) {
$this->params = $params;
$this->method = $method;
$this->endpoint = $endpoint;
$client = new Client();
try{
$response = $client->request($this->method, $this->endpoint,['json' => $this->params]);
$this->body = $response->getBody();
$this->status = $response->getStatusCode();
}
catch(ClientException $e){
$this->body = $e->getMessage();
$this->status = $e->getCode();
$this->errorJson = $e->getResponse()->getBody();
}
}
function getStatus(){
return $this->status;
}
function getBody(){
return $this->body;
}
function getErrorJson(){
return $this->errorJson;
}
}
updateLiveMedia.php
namespace models;
use myGuzzle\myGuzzle;
class updateLiveMedia {
protected $username;
protected $access_token;
public $media_user_name;
public $media_id;
public $media_category_id;
public $media_live_delay;
public $media_hidden;
public $media_recording;
public $media_mature;
public $media_hosted_name;
public $media_countries = [];
public $media_status;
public $media_description;
function __construct($username,$acessToken) {
$this->username = $username;
$this->access_token = $acessToken;
$this->media_countries = ["EN"];
$this->media_category_id = '0';
$this->media_recording = "1";
$this->media_mature ='0';
$this->media_user_name = $this->username;
$this->media_hosted_name = "on";
$this->media_live_delay='2';
$this->media_hidden = '0';
}
/*
* Parameters function(title,descritption) or
* Parameters function(title,description,countries).
*/
function update($opts=[]){
if(is_array($opts)){
foreach($opts as $key=>$value){
$this->$key = $value;
}
}
$myGuzzle = new myGuzzle;
$uri = "https://api.hitbox.tv/media/live/".$this->username."?authToken=".$this->access_token;
$params = ["livestream"=>[
[
"media_user_name"=>$this->username,
"media_id"=>$this->media_id,
"media_category_id"=>$this->media_category_id,
"media_live_delay"=>$this->media_live_delay,
"media_hidden"=>$this->media_hidden,
"media_recording"=>$this->media_recording,
"media_mature"=>$this->media_mature,
"media_hosted_name"=>$this->media_hosted_name,
"media_countries"=> $this->media_countries,
"media_status"=>$this->media_status,
"media_description"=>$this->media_description,
]
]];
$myGuzzle->response($params,"PUT",$uri);
return $myGuzzle->getBody();
if($myGuzzle->getStatus() == 200){
return true;
}
return false;
}
}
run update() return the following result
{"success":true,"error":false,"message":"host_mode_enabled"}
According the documentation provided by hitbox.tv (http://developers.hitbox.tv/#update-live-media) should return something like the following
{
"livestream": [
{
"media_user_name": "masta",
"media_id": "1",
"media_category_id": "447",
"media_live_delay": "0",
"media_hidden": "0",
"media_recording": "1",
"media_mature": "0",
"media_countries": [
"EN"
],
"media_status": "This is a stream title!",
"media_description_md": null
}
]
}
The API documentation for update-live-media describes the response to be expected only when the hosting flag is not being modified.
The other cases would return different types of answers:
When host mode is being enabled
{"success":true,"error":false,"message":"host_mode_enabled"}
When host mode is being disabled
{"success":true,"error":false,"message":"host_mode_disabled"}
And an error would also return something different than the documentation, for example:
If the authToken/media_id is invalid or there is insufficient permissions
{"success":true,"error":false,"message":"auth_required_token"} or
{"success":true,"error":false,"message":"auth_required"}
When you are updating the hosting mode, you won't be updating any other information (title). Just try to leave out media_hosted_name from your query, and watch how description and titles get updated. Users (and chat bots) then can see the updated information.
Let me know if you still are having any trouble with the API. I would need a specific example and could help you further.
I am attempting to add logging for the envelope generated by a third party library. I am modifying the updateMetadataField() method below.
I am creating $client like so:
$client = new UpdateClient($UPDATE_END_POINT, $USER_AUTH_ARRAY);
I have tried both $this->client->__getLastRequest() and $this->__getLastRequest() with the same error as a result.
When the SoapClient is instantiated trace is set to true.
Error is
Fatal error: Call to undefined method UpdateClient::__getLastRequest()
So how do I correctly access the __getLastRequest() method?
$USER_AUTH_ARRAY = array(
'login'=>"foo",
'password'=>"bar",
'exceptions'=>0,
'trace'=>true,
'features' => SOAP_SINGLE_ELEMENT_ARRAYS
);
class UpdateClient {
private $client;
public function __construct($endpoint, $auth_array) {
$this->client = new SoapClient($endpoint, $auth_array);
}
public function updateMetadataField($uuid, $key, $value) {
$result = $this->client->updateMetadataField(array(
'assetUuid' => $uuid,
'key' => $key,
'value' => $value)
);
if(is_soap_fault($result)) {
return $result;
}
return $result->return . "\n\n" . $this->client->__getLastRequest();
} // updateMetadataField()
} // UpdateClient
UPDATE - adding calling code This code iterates over an array which maps our data to the remote fields.
What I am hoping to do is begin storing the envelope we send to aid in debugging.
$client = new UpdateClient($UPDATE_END_POINT, $USER_AUTH_ARRAY);
foreach ($widen_to_nool_meta_map as $widen => $nool) { // array defined in widen.php
if ($nool != '') {
// handle exceptions
if ($nool == 'asset_created') { // validate as date - note that Widen pulls exif data so we don't need to pass this
if (!strtotime($sa->$nool)) {
continue;
}
} else if ($nool == 'people_in_photo' || $nool == 'allow_sublicensing' || $nool == 'allowed_use_pr_gallery') {
// we store as 0/1 but GUI at Widen wants Yes/No
$sa->$nool = ($sa->$nool == '1') ? 'Yes' : 'No';
} else if ($nool == 'credit_requirements') {
$sa->$nool = $sa->credit_requirements()->label;
}
$result = $client->updateMetadataField($sa->widen_id, $widen, $sa->$nool);
if(is_soap_fault($result)) {
$sync_result = $sync_result . "\n" . $result->getMessage();
} else {
$sync_result = $sync_result . "\n" . print_r($result, 1);
}
} // nool field set
} // foreach mapped field
If you want to access UpdateClient::__getLastRequest() you have to expose that method on the UpdateClient class since the $client is a private variable. The correct way of calling it is $this->client->__getLastRequest().
Take a look at this working example, as you can see I'm consuming a free web service for testing purposes.
<?php
$USER_AUTH_ARRAY = array(
'exceptions'=>0,
'trace'=>true,
'features' => SOAP_SINGLE_ELEMENT_ARRAYS
);
class TestClient {
private $client;
public function __construct($endpoint, $auth_array) {
$this->client = new SoapClient($endpoint, $auth_array);
}
public function CelsiusToFahrenheit( $celsius ) {
$result = $this->client->CelsiusToFahrenheit(array(
'Celsius' => $celsius
)
);
if(is_soap_fault($result)) {
return $result;
}
return $result;
}
public function __getLastRequest() {
return $this->client->__getLastRequest();
}
}
try
{
$test = new TestClient( "http://www.w3schools.com/webservices/tempconvert.asmx?wsdl", $USER_AUTH_ARRAY);
echo "<pre>";
var_dump($test->CelsiusToFahrenheit( 0 ));
var_dump($test->__getLastRequest());
var_dump($test->CelsiusToFahrenheit( 20 ));
var_dump($test->__getLastRequest());
echo "</pre>";
}
catch (SoapFault $fault)
{
echo $fault->faultcode;
}
?>