Doxygen does not document one PHP class - php

I have a small Symfony project I want to document with doxygen. There are two .php files that should be included. One is documented, the other is not, and I cannot figure out why that may be.
Folder structure is:
project
└--src
|--Controller
| └--FormController.php
└--Model
└--Inquiry.php
Doxygen is reading and parsing both files...
Reading /form-handler/src/Controller/FormController.php...
Parsing file /form-handler/src/Controller/FormController.php...
Reading /form-handler/src/Model/Inquiry.php...
Parsing file /form-handler/src/Model/Inquiry.php...
...but only documents FormController.php, not Inquiry.php:
Generating docs for compound App::Controller::FormController...
For some reason doxygen does not seem to recognizeInquiry.php as a class.
What I have tried:
Removed decorators from docstrings that might offend doxygen.
Checked format of docstrings
Enabled/disabled various Doxyfile options
FormController.php:
<?php
declare(strict_types=1);
namespace App\Controller;
use App\Model\Inquiry;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\RateLimiter\RateLimiterFactory;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
/**
* Handles incoming requests.
*/
class FormController extends AbstractController
{
/**
* Handles POST requests.
*
* #return Response contains JSON object with 'message' value
*/
#[Route('/', methods: ['POST'])]
public function handleRequest(
HttpClientInterface $client,
Inquiry $inquiry,
LoggerInterface $logger,
RateLimiterFactory $formApiLimiter,
Request $request,
): Response {
$logger->debug('Received a POST request');
// set up a rate limiter by IP
// rules are defined in /config/packages/rate-limiter.yaml
$limiter = $formApiLimiter->create($request->getClientIp());
$limit = $limiter->consume();
// configure headers exposing rate limit info
$headers = [
'Content-Type' => 'application/json',
'X-RateLimit-Remaining' => $limit->getRemainingTokens(),
'X-RateLimit-Retry-After' => $limit->getRetryAfter()->getTimestamp(),
'X-RateLimit-Limit' => $limit->getLimit(),
];
if (false === $limit->isAccepted()) {
return new Response(
content: json_encode(['message' => null]),
status: Response::HTTP_TOO_MANY_REQUESTS,
headers: $headers
);
}
// make sure all required fields are included in request and not empty
$requiredFields = ['subject', 'message', 'consent', 'h-captcha-response'];
$providedFields = $request->request->keys();
foreach ($requiredFields as $field) {
if (!in_array($field, $providedFields)) {
return new Response(
content: json_encode(['message' => "Pflichtfeld '".$field."' fehlt."]),
status: Response::HTTP_BAD_REQUEST,
headers: $headers
);
} elseif ('' == filter_var($request->request->get($field), FILTER_SANITIZE_SPECIAL_CHARS)) {
return new Response(
content: json_encode(['message' => "Pflichtfeld '".$field."' darf nicht leer sein."]),
status: Response::HTTP_BAD_REQUEST,
headers: $headers
);
}
}
// verify captcha success
$captcha = filter_var($request->request->get('h-captcha-response'), FILTER_SANITIZE_SPECIAL_CHARS);
$data = [
'secret' => $this->getParameter('app.captcha.secret'),
'response' => $captcha,
];
try {
$hCaptchaResponse = $client->request(
method: 'POST',
url: 'https://hcaptcha.com/siteverify',
options: [
'body' => $data,
],
);
$hCaptchaResponseJson = json_decode($hCaptchaResponse->getContent(), true);
if (!$hCaptchaResponseJson['success']) {
return new Response(
content: json_encode(['message' => 'Captcha fehlgeschlagen']),
status: Response::HTTP_BAD_REQUEST,
headers: $headers
);
}
// exceptions on the side of hCaptcha are logged, but the request is processed anyway
} catch (TransportExceptionInterface $e) {
$logger->debug('Could not reach hCaptcha verification server: '.$e);
} catch (ClientExceptionInterface|RedirectionExceptionInterface|ServerExceptionInterface $e) {
$logger->debug('Error when verifying hCaptcha response: '.$e);
}
// get values from request data
$name = filter_var($request->request->get('name'), FILTER_SANITIZE_SPECIAL_CHARS);
$email = filter_var($request->request->get('email'), FILTER_SANITIZE_EMAIL);
$phone = filter_var($request->request->get('phone'), FILTER_SANITIZE_SPECIAL_CHARS);
$subject = filter_var($request->request->get('subject'), FILTER_SANITIZE_SPECIAL_CHARS);
$message = filter_var($request->request->get('message'), FILTER_SANITIZE_SPECIAL_CHARS);
$consent = filter_var($request->request->get('consent'), FILTER_SANITIZE_SPECIAL_CHARS);
// translate into a boolean (else the string 'false' will be evaluated as true)
$consent = filter_var($consent, FILTER_VALIDATE_BOOLEAN);
// populate Inquiry with request data
$inquiry->createInquiry(
subject: $subject,
message: $message,
consent: $consent,
name: $name,
email: $email,
phone: $phone,
);
// validate Inquiry
$validationResult = $inquiry->validateInquiry();
// if Inquiry is invalid, return validation violation message(s)
if (count($validationResult) > 0) {
$logger->debug($validationResult);
// assemble list of error messages
$validationMessages = [];
foreach ($validationResult as $result) {
$validationMessages += [$result->getPropertyPath() => $result->getMessage()];
}
return new Response(
content: json_encode([
'message' => 'Anfrage enthält ungültige Werte',
'errors' => $validationMessages,
]),
status: Response::HTTP_BAD_REQUEST,
headers: $headers
);
}
// send mail to office
$emailResult = $inquiry->sendOfficeEmail();
$logger->debug(implode(' ,', $emailResult));
$message = 'Die Anfrage war erfolgreich';
if (!$emailResult['success']) {
$message = 'Die Anfrage war nicht erfolgreich.';
}
// TODO compile email to user
$data = [
'message' => $message,
'officeEmail' => $emailResult,
'confirmationEmail' => true,
];
return new Response(
content: json_encode($data),
status: Response::HTTP_OK,
headers: $headers
);
}
/**
* Handles disallowed request methods.
*
* #return Response contains JSON object with 'message' value
*/
#[Route('/', condition: "context.getMethod() not in ['POST']")]
public function handleDisallowedMethods(LoggerInterface $logger): Response
{
$logger->debug('Received a request with a disallowed method.');
return new Response(
content: json_encode(['message' => 'Only POST requests allowed']),
status: Response::HTTP_METHOD_NOT_ALLOWED
);
}
}
Inquiry.php:
<?php
declare(strict_types=1);
namespace App\Model;
use Psr\Log\LoggerInterface;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
use Symfony\Component\Validator\ConstraintViolationListInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* Represents an inquiry received from the front end.
*
* The required fields 'subject', 'message', and
* 'consent' must be provided to the constructor.
*/
class Inquiry
{
private string $name;
private string $email;
private string $phone;
private string $subject;
private string $message;
private bool $consent;
private bool $validated = false;
/**
* #param LoggerInterface $logger
* #param MailerInterface $mailer
* #param array $officeRecipients configured in services.yaml
* #param ValidatorInterface $validator
*/
public function __construct(
private readonly LoggerInterface $logger,
private readonly MailerInterface $mailer,
private readonly array $officeRecipients,
private readonly ValidatorInterface $validator,
) {
}
/**
* Populates an inquiry with data.
*
* 'subject', 'message', and 'consent are required,
* all other values are optional.
*
* Sets 'validated' to false, in case createInquiry()
* is called multiple times.
*/
public function createInquiry(
string $subject,
string $message,
bool $consent,
string $name = '',
string $email = '',
string $phone = '',
): void {
$this->subject = $subject;
$this->message = $message;
$this->consent = $consent;
$this->name = $name;
$this->email = $email;
$this->phone = $phone;
$this->validated = false;
}
/**
* Validates the inquiry.
*
* If successful, sets 'validated' to true
*
* #return ConstraintViolationListInterface if valid: empty
* if not valid: list of validation violation messages
*/
public function validateInquiry(): ConstraintViolationListInterface
{
$validationResult = $this->validator->validate($this);
if (0 == count($validationResult)) {
$this->validated = true;
}
return $validationResult;
}
/**
* Sends an email with the customer inquiry data to the office.
*
* #return array containing 'success' boolean and 'message' string
*/
public function sendOfficeEmail(): array
{
if (!$this->validated) {
return [
'success' => false,
'message' => 'Inquiry has not been validated. Use Inquiry->validate() first',
];
}
// convert 'consent' and empty values in to human-readable format
$plainTextConsent = $this->consent ? 'Ja' : 'Nein';
$plainTextName = $this->name ?: 'Keine Angabe';
$plainTextEmail = $this->email ?: 'Keine Angabe';
$plainTextPhone = $this->phone ?: 'Keine Angabe';
$emailBody = <<<END
Das Kontaktformular hat eine Anfrage erhalten.
Betreff: $this->subject
Nachricht: $this->message
Einwilligung: $plainTextConsent
Name: $plainTextName
Email: $plainTextEmail
Telefon: $plainTextPhone
END;
$email = (new Email())
->to(...$this->officeRecipients)
->subject('Anfrage vom Kontaktformular')
->text($emailBody);
try {
$this->mailer->send($email);
$this->logger->debug('Email sent');
return [
'success' => true,
'message' => 'Email wurde gesendet',
];
} catch (TransportExceptionInterface $e) {
$this->logger->debug('Error sending email: '.$e);
return [
'success' => false,
'message' => 'Email konnte nicht gesendet werden: '.$e,
];
}
}
/**
* #codeCoverageIgnore
*/
public function sendConfirmationEmail(): string
{
return '';
}
/**
* Checks whether Inquiry has been validated.
*/
public function isValidated(): bool
{
return $this->validated;
}
}
Doxyfile (EDITED as per #albert's comment):
# Difference with default Doxyfile 1.9.3 (c0b9eafbfb53286ce31e75e2b6c976ee4d345473)
PROJECT_NAME = "Form handler"
PROJECT_BRIEF = "Stand-alone Symfony backend to handle contact forms."
OUTPUT_DIRECTORY = ./doc/
INPUT = ./src/ \
README.md
RECURSIVE = YES
EXCLUDE = ./src/Kernel.php
USE_MDFILE_AS_MAINPAGE = README.md
GENERATE_LATEX = NO

As of PHP version 7.3.0 the syntax of the here document changed slightly, see https://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.heredoc
The closing identifier may be indented by space or tab, in which case the indentation will be stripped from all lines in the doc string. Prior to PHP 7.3.0, the closing identifier must begin in the first column of the line.
This has now been corrected in the proposed patch, pull request: https://github.com/doxygen/doxygen/pull/9398
Workarounds:
place the end identifier of the here document at the beginning of the line
place a doxygen conditional block /** \cond / / /* \endcond */ around the here document.

Related

string(331) "Legacy People API has not been used

I'm getting this error when i try to register via google api
string(331) "Legacy People API has not been used in project ******* before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/legacypeople.googleapis.com/overview?project=******** then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry."
And when i go that url i'm receiving
Failed to load.
There was an error while loading /apis/api/legacypeople.googleapis.com/overview?project=******&dcccrf=1. Please try again.
My google.php code in /vendor/league/oauth2-google/src/Provider is
<?php
namespace League\OAuth2\Client\Provider;
use League\OAuth2\Client\Exception\HostedDomainException;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Token\AccessToken;
use League\OAuth2\Client\Tool\BearerAuthorizationTrait;
use Psr\Http\Message\ResponseInterface;
class Google extends AbstractProvider
{
use BearerAuthorizationTrait;
const ACCESS_TOKEN_RESOURCE_OWNER_ID = 'id';
/**
* #var string If set, this will be sent to google as the "access_type" parameter.
* #link https://developers.google.com/accounts/docs/OAuth2WebServer#offline
*/
protected $accessType;
/**
* #var string If set, this will be sent to google as the "hd" parameter.
* #link https://developers.google.com/accounts/docs/OAuth2Login#hd-param
*/
protected $hostedDomain;
/**
* #var array Default fields to be requested from the user profile.
* #link https://developers.google.com/+/web/api/rest/latest/people
*/
protected $defaultUserFields = [
'id',
'name(familyName,givenName)',
'displayName',
'emails/value',
'image/url',
];
/**
* #var array Additional fields to be requested from the user profile.
* If set, these values will be included with the defaults.
*/
protected $userFields = [];
/**
* Use OpenID Connect endpoints for getting the user info/resource owner
* #var bool
*/
protected $useOidcMode = false;
public function getBaseAuthorizationUrl()
{
return 'https://accounts.google.com/o/oauth2/auth';
}
public function getBaseAccessTokenUrl(array $params)
{
return 'https://www.googleapis.com/oauth2/v4/token';
}
public function getResourceOwnerDetailsUrl(AccessToken $token)
{
if ($this->useOidcMode) {
// OIDC endpoints can be found https://accounts.google.com/.well-known/openid-configuration
return 'https://www.googleapis.com/oauth2/v3/userinfo';
}
// fields that are required based on other configuration options
$configurationUserFields = [];
if (isset($this->hostedDomain)) {
$configurationUserFields[] = 'domain';
}
$fields = array_merge($this->defaultUserFields, $this->userFields, $configurationUserFields);
return 'https://www.googleapis.com/plus/v1/people/me?' . http_build_query([
'fields' => implode(',', $fields),
'alt' => 'json',
]);
}
protected function getAuthorizationParameters(array $options)
{
$params = array_merge(
parent::getAuthorizationParameters($options),
array_filter([
'hd' => $this->hostedDomain,
'access_type' => $this->accessType,
// if the user is logged in with more than one account ask which one to use for the login!
'authuser' => '-1'
])
);
return $params;
}
protected function getDefaultScopes()
{
return [
'email',
'openid',
'profile',
];
}
protected function getScopeSeparator()
{
return ' ';
}
protected function checkResponse(ResponseInterface $response, $data)
{
if (!empty($data['error'])) {
$code = 0;
$error = $data['error'];
if (is_array($error)) {
$code = $error['code'];
$error = $error['message'];
}
throw new IdentityProviderException($error, $code, $data);
}
}
protected function createResourceOwner(array $response, AccessToken $token)
{
$user = new GoogleUser($response);
// Validate hosted domain incase the user edited the initial authorization code grant request
if ($this->hostedDomain === '*') {
if (empty($user->getHostedDomain())) {
throw HostedDomainException::notMatchingDomain($this->hostedDomain);
}
} elseif (!empty($this->hostedDomain) && $this->hostedDomain !== $user->getHostedDomain()) {
throw HostedDomainException::notMatchingDomain($this->hostedDomain);
}
return $user;
}
}
How to fix this issue?
Legacy People API has not been used in project ******* before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/legacypeople.googleapis.com/overview?project=********
As the error message states you have not enabled the people api in your project and as you have included email and profile and are trying to request profiled data about the user.
return 'https://www.googleapis.com/plus/v1/people/me?' . http_build_query([
'fields' => implode(',', $fields),
'alt' => 'json',
You need to enable the people api in our project before you can request data. Click the link and follow the instructions below.
Go to Google developer console click library on the left. Then search for the API you are looking to use and click enable button
Wait a couple of minutes then run your code again. Then you will be able to make requests to the people api.
return 'https://www.googleapis.com/plus/v1/people/me?' . http_build_query([
'fields' => implode(',', $fields),
'alt' => 'json',
Legacy endpoint:
I also recommend up update your endpoint to the new people.get endpoint
https://people.googleapis.com/v1/people/me

What parameters do I pass to doExpressCheckouPaymentt()?

I have this controller that is supposed to perform PayPal payments. The payment function is working well but on getting to success function I am getting an error Illegal string offset 'total' . I am passing $this->productData($request) as suggested in this question. I tried creating a variable $total = $response['AMT'] which is the response from setCheckoutDetails but I still got the same error. How do I go about it?
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Srmklive\PayPal\Services\ExpressCheckout;
class PayPalController extends Controller
{
private function projectData(Request $request){
// dd($request->all());
$item = [];
$datat = array_map(function($item){
return [
'name'=>$request->project_id,
'price'=>$request->budget,
'desc'=>'Deposit',
'qty'=>1
];
}, $item);
$data = [
'items'=>$datat,
'invoice_id' => uniqid(),
'invoice_description' => "Payment for Project No.".$request->project_id." Amount ".$request->budget,
'return_url' => route('payment.success'),
'cancel_url' => route('payment.cancel'),
'total'=>$request->budget
];
// dd($data);
return $data;
}
/**
* Responds with a welcome message with instructions
*
* #return \Illuminate\Http\Response
*/
public function payment(Request $request) {
$data = $this->projectData($request);
$provider = new ExpressCheckout;
$response = $provider->setExpressCheckout($data);
// dd($response);
// $response = $provider->setExpressCheckout($data, true);
return redirect($response['paypal_link']);
}
/**
* Responds with a welcome message with instructions
*
* #return \Illuminate\Http\Response
*/
public function cancel()
{
dd('Your payment is canceled. You can create cancel page here.');
}
/**
* Responds with a welcome message with instructions
*
* #return \Illuminate\Http\Response
*/
public function success(Request $request)
{
$provider = new ExpressCheckout;
$response = $provider->getExpressCheckoutDetails($request->token);
$token = $response['TOKEN'];
$payerId = $response['PAYERID'];
$total = $response['AMT'];
// dd($response);
if (in_array(strtoupper($response['ACK']), ['SUCCESS', 'SUCCESSWITHWARNING'])) {
// dd('Payment successful');
//Performing transaction
$payment_status = $provider->doExpressCheckoutPayment($token, $payerId, $this->projectData($request));
dd($payment_status);
}
dd('Something is wrong.');
}
}
You have to pass three parameters
data, token, PAYERID
Data can service information like
$data = array(
'total' => Total amount,
'invoice_id' => Invoicen number,
'invoice_description' => invoice descrption
);
And items as well which will contain name, price, desc and qty

How to send two different emails on one button click using Laravel

I am trying to send two emails at the same time when the user submits contact form. One email to the website owner and other to the user as autoresponse. I have been trying to do this for about last 4 hours and tried different solutions on internet but I am totally lost. Here is my code to send an email
public function contactForm(Request $request)
{
$parameters = Input::get();
$email = Input::get('email');
$inquiryType = Input::get('type_inquiry');
foreach ([
'contactmessage' => 'Message',
'email' => 'Email',
'phone' => 'Phone',
'first_name' => 'Contact Name',
'g-recaptcha-response' => 'Captcha',
] as $key => $label) {
if (!isset($parameters[$key]) || empty($parameters[$key])) {
return response()->json(
[
'success' => false,
'error' => "{$label} cannot be empty",
]
);
}
}
$recipients = 'abc#gmail.com';
// if page set, try to get recipients from the page settings
if (Input::get('page_id')) {
$page = Page::find(Input::get('page_id'));
if ($page && !empty($page->recipients)) {
$recipients = explode(',', $page->recipients);
}
}
try {
$res = Mail::send(
'emails.contact',
$parameters,
function (Message $message) use ($recipients) {
$message->subject('Contact message');
if (is_array($recipients)) {
// email to first address
$message->to(array_shift($recipients));
// cc others
$message->cc($recipients);
} else {
$message->to($recipients);
}
}
);
} catch (\Exception $e) {
return response()->json(
[
'success' => false,
'error' => $e->getMessage(),
]
);
}
if($inquiryType == 'Rental Inquiry'){
Mail::send(
'emails.autoresponse',
'',
function (Message $message) use ($email) {
$message->subject('Thank you for inquiring');
if (is_array($email) {
// email to first address
$message->to(array_shift($email);
// cc others
$message->cc($email);
} else {
$message->to($email);
}
}
);
}
return response()->json(
[
'success' => $res,
]
);
}
I have tried to do the same thing by different methods but none of them are working. Please help me. This is the first time I am sending multiple emails using laravel. I think I am doing a big and silly mistake somewhere.
Thank you.
You have a missing closing parenthesis near is_array($email)
$message->subject('Thank you for inquiring');
if (is_array($email)) {
Also i would you use laravel's validator to check for required input. Another suggestion would be to use queues for mails. Sending two mails in a single request might cause your page load time to increase significantly.
The best way is create one Laravel Jobs
php artisan queue:table
php artisan migrate
php artisan make:job SendEmail
Edit your .env
QUEUE_DRIVER=database
Edit your app /Jobs/SendEmail.php
namespace App\Jobs;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Mail\Mailer;
class SendEmail extends Job implements ShouldQueue
{
use InteractsWithQueue, SerializesModels;
protected $subject;
protected $view;
protected $data;
protected $email;
/**
* SendEmail constructor.
* #param $subject
* #param $data
* #param $view
* #param $email
*/
public function __construct($subject, $data, $view, $email)
{
$this->subject = $subject;
$this->data = $data;
$this->email = $email;
$this->view = $view;
}
/**
* Execute the job.
* #param $mailer
* #return void
*/
public function handle(Mailer $mailer)
{
$email = $this->email;
$subject = $this->subject;
$view = $this->view;
$mailer->send($view, $this->data,
function ($message) use ($email, $subject) {
$message->to($email)
->subject($subject);
}
);
}
}
And handle in your controller
use App\Jobs\SendEmail;
public function contactForm(Request $request) {
//TODO Configure Subject
$subjectOwner = 'Your Email Subject For Owner';
$subjectUser = 'Your Email Subject For User';
//TODO Configure Email
$ownerEmail = 'ownerEmail#gmail.com';
$userEmail = 'userEmail#gmail.com';
//TODO Configure Data Email send to email blade viewer
$dataEmail = [
'lang' => 'en',
'user_name' => 'User Name'
];
//emails.owner mean emails/owner.blade.php
//emails.admin mean emails/admin.blade.php
$jobOwner = (new SendEmail($subjectOwner, $dataEmail, "emails.owner" , $ownerEmail))->onQueue('emails');
dispatch($jobOwner);
$jobUser = (new SendEmail($subjectUser, $dataEmail, "emails.admin" , $userEmail))->onQueue('emails');
dispatch($jobUser);
}
And try command
//IF You using Laravel 5.2
php artisan queue:listen --queue=emails
//IF You using Laravel >5.3
php artisan queue:work

How to collect tweets/retweets using GNIP API in php

Since twitter has switched to streaming his API, we have to collect the data by ourselves.
How we can do it using GNIP API in php?
Answering this, I’ve just wanted to ensure that I’ve done everything right and maybe to improve my TwitterGnipClient class with your help.
But if there are no answers, let it be FAQ style question.
Main methods see below:
/**
* Add rules to PowerTrack stream’s ruleset.
* #example ['id' => 'url', ...]
* #param array $data
*/
public function addRules(array $data)
{
$rules = [];
foreach ($data as $id => $url) {
$rules[] = $this->buildRuleForUrl($url, $id);
}
$this->httpClient->post($this->getRulesUrl(), [
'auth' => [$this->getUser(), $this->getPassword()],
'json' => ['rules' => $rules]
]);
}
/**
* Retrieves all existing rules for a stream.
* #return \Generator
*/
public function getRules()
{
$response = $this->httpClient->get($this->getRulesUrl(), [
'auth' => [$this->getUser(), $this->getPassword()]
]);
$batchStr = '';
$body = $response->getBody();
while (!$body->eof()) {
$batchStr .= $body->read(1024);
}
$batchArray = explode(PHP_EOL, $batchStr);
unset($batchStr);
foreach ($batchArray as $itemJson) {
yield $this->unpackJson($itemJson);
}
}
/**
* Removes the specified rules from the stream.
* #param $data
*/
public function deleteRules($data)
{
$rules = [];
foreach ($data as $id => $url) {
$rules[] = $this->buildRuleForUrl($url, $id);
}
$this->httpClient->delete($this->getRulesUrl(), [
'auth' => [$this->getUser(), $this->getPassword()],
'json' => ['rules' => array_values($rules)]
]);
}
/**
* Open stream through which the social data will be delivered.
* #return \Generator
*/
public function listenStream()
{
$response = $this->httpClient->get($this->getStreamUrl(), [
'auth' => [$this->getUser(), $this->getPassword()],
'stream' => true
]);
$batchStr = '';
$body = $response->getBody();
while (!$body->eof()) {
$batchStr .= $body->read(1024);
$batchArray = explode(PHP_EOL, $batchStr);
// leave the last piece of response as it can be incomplete
$batchStr = array_pop($batchArray);
foreach ($batchArray as $itemJson) {
yield $this->processBroadcastItem($this->unpackJson($itemJson));
}
}
$body->close();
}
/**
* Process broadcast item data
* #param $data
* #return array
*/
protected function processBroadcastItem($data)
{
if (is_array($data)) {
$url = str_replace('url_contains:', '', $data['gnip']['matching_rules'][0]['value']);
switch ($data['verb']) {
// Occurs when a user posts a new Tweet.
case 'post':
return $this->getMappedResponse($url, 'tweet', 1);
break;
// Occurs when a user Retweets another user's Tweet
case 'share':
return $this->getMappedResponse($url, 'retweet', $data['retweetCount']);
break;
}
}
return [];
}
All class I shared as Gist - here
P.S. If you see an evident issue - comment it please.

Android push (GCM) from App Engine using Zend Framework

I'm trying to implement GCM server using PHP and Zend Framework on Google App Engine. So far it works fine locally, but fails with this message when uploaded to App Engine:
Here is the code:
$ids = '....my device id....';
$apiKey = '...my api key...';
$data = array( 'message' => 'Hello World!' );
$gcm_client = new Client();
$gcm_client->setApiKey($apiKey);
$gcm_message = new Message();
$gcm_message->setTimeToLive(86400);
$gcm_message->setData($data);
$gcm_message->setRegistrationIds($ids);
$response = $gcm_client->send($gcm_message);
var_dump($response);
And it fails with this error message:
PHP Fatal error: Uncaught exception 'ErrorException' with message
'stream_socket_client(): unable to connect to
android.googleapis.com:443 (Unknown error 4294967295)' in
/base/data/home/..../backend:v1.375711862873219029/vendor/zendframework/zend-http/Zend/Http/Client/Adapter/Socket.php:253
I know App Engine doesn't allow socket connections and offers urlFetch wrapper for http and https, but how do I tell Zend Framework to use this transport?
Try enabling Billing. As far as I remember sockets are enabled only for paid apps.
This won't charge you anything (unless you exceed free quota) but should get rid of the error.
Promoted this from a comment - I ended up making my own class implementing Zend\Http\Client\Adapter\AdapterInterface that uses URLFetch by opening a URL using the usual fopen with stream context to send POST request. Although this works I'm not sure it's the best way. Would prefer to use the framework capabilities, if possible.
I'm not sure if this is going to help anyone, as both ZendFramework and AppEngine have evolved since I asked the question, but here is the adapter I've implemented:
use Zend\Http\Client\Adapter\AdapterInterface;
use Zend\Http\Client\Adapter\Exception\RuntimeException;
use Zend\Http\Client\Adapter\Exception\TimeoutException;
use Zend\Stdlib\ErrorHandler;
class URLFetchHttpAdapter implements AdapterInterface
{
protected $stream;
protected $options;
/**
* Set the configuration array for the adapter
*
* #param array $options
*/
public function setOptions($options = array())
{
$this->options = $options;
}
/**
* Connect to the remote server
*
* #param string $host
* #param int $port
* #param bool $secure
*/
public function connect($host, $port = 80, $secure = false)
{
// no connection yet - it's performed in "write" method
}
/**
* Send request to the remote server
*
* #param string $method
* #param \Zend\Uri\Uri $url
* #param string $httpVer
* #param array $headers
* #param string $body
*
* #throws \Zend\Loader\Exception\RuntimeException
* #return string Request as text
*/
public function write($method, $url, $httpVer = '1.1', $headers = array(), $body = '')
{
$headers_str = '';
foreach ($headers as $k => $v) {
if (is_string($k))
$v = ucfirst($k) . ": $v";
$headers_str .= "$v\r\n";
}
if (!is_array($this->options))
$this->options = array();
$context_arr = array("http" =>
array( "method" => $method,
"content" => $body,
"header" => $headers_str,
"protocol_version" => $httpVer,
'ignore_errors' => true,
'follow_location' => false,
) + $this->options
);
$context = stream_context_create($context_arr);
ErrorHandler::start();
$this->stream = fopen((string)$url, 'r', null, $context);
$error = ErrorHandler::stop();
if (!$this->stream) {
throw new \Zend\Loader\Exception\RuntimeException('', 0, $error);
}
}
/**
* Read response from server
*
* #throws \Zend\Http\Client\Adapter\Exception\RuntimeException
* #return string
*/
public function read()
{
if ($this->stream) {
ErrorHandler::start();
$metadata = stream_get_meta_data($this->stream);
$headers = join("\r\n", $metadata['wrapper_data']);
$contents = stream_get_contents($this->stream);
$error = ErrorHandler::stop();
if ($error)
throw $error;
$this->close();
//echo $headers."\r\n\r\n".$contents;
return $headers."\r\n\r\n".$contents;
} else {
throw new RuntimeException("No connection exists");
}
}
/**
* Close the connection to the server
*
*/
public function close()
{
if (is_resource($this->stream)) {
ErrorHandler::start();
fclose($this->stream);
ErrorHandler::stop();
$this->stream = null;
}
}
/**
* Check if the socket has timed out - if so close connection and throw
* an exception
*
* #throws TimeoutException with READ_TIMEOUT code
*/
protected function _checkSocketReadTimeout()
{
if ($this->stream) {
$info = stream_get_meta_data($this->stream);
$timedout = $info['timed_out'];
if ($timedout) {
$this->close();
throw new TimeoutException(
"Read timed out after {$this->options['timeout']} seconds",
TimeoutException::READ_TIMEOUT
);
}
}
}
}
public function sendAndroidPushNotification($registration_ids, $message)
{
$registrationIds = array($registration_ids);
$msg = array(
'message' => $message,
'title' => 'notification center',
'vibrate' => 1,
'sound' => 1
);
$fields = array(
'registration_ids' => $registrationIds,
'data' => $msg
);
$fields = json_encode($fields);
$arrContextOptions=array(
"http" => array(
"method" => "POST",
"header" =>
"Authorization: key = <YOUR_APP_KEY>". "\r\n" .
"Content-Type: application/json". "\r\n",
"content" => $fields,
),
"ssl"=>array(
"allow_self_signed"=>true,
"verify_peer"=>false,
),
);
$arrContextOptions = stream_context_create($arrContextOptions);
$result = file_get_contents('https://android.googleapis.com/gcm/send', false, $arrContextOptions);
return $result;
}

Categories