In my Laravel application I am using the HubSpot API extensively to perform various actions. I have read in the documentation that you can make 150 requests per each 10 second period.
To monitor this HubSpot provide the following Headers when making any API call.
"X-HubSpot-RateLimit-Daily" => array:1 [▶]
"X-HubSpot-RateLimit-Daily-Remaining" => array:1 [▶]
"X-HubSpot-RateLimit-Interval-Milliseconds" => array:1 [▶]
"X-HubSpot-RateLimit-Max" => array:1 [▶]
"X-HubSpot-RateLimit-Remaining" => array:1 [▶]
"X-HubSpot-RateLimit-Secondly" => array:1 [▶]
"X-HubSpot-RateLimit-Secondly-Remaining" => array:1 [▶]
In my application I am making use of Laravel's Http Client, which is basically just a wrapper for Guzzle.
In order to adhere to the rate limits would I literally just have to wrap an if statement around every request?
Here's an example:
$endpoint = 'https://api.hubapi.com/crm/v3/owners/';
$response = Http::get($endpoint, [
'limit' => 100,
'hapikey' => config('hubspot.api_key'),
]);
In this case the $response would contain the Headers but would there be a way to effectively use them, as surely I would only know what the rates were once I'd made the API call?
I ask as I have to pull down 1,000 + deals and then update some records, but this would definately go over the API limit. For reference, here is the command I wrote.
<?php
namespace App\Console\Commands;
use App\Events\DealImportedFromHubspot;
use App\Hubspot\PipelineHubspot;
use App\Models\Deal;
use App\Models\DealStage;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;
class ImportHubspotDeals extends Command
{
/**
* The name and signature of the console command.
*
* #var string
*/
protected $signature = 'import:hubspot-deals
{--force : Whether we should force the command}
';
/**
* The console command description.
*
* #var string
*/
protected $description = 'Import Deal objects from the HubSpot API in bulk.';
/**
* An array to store imported Deals
*
* #var array
*/
private $importedDeals = [];
/**
* Create a new command instance.
*
* #return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* #return int
*/
public function handle()
{
$this->line('Importing Pipelines & Deal Stages from HubSpot API...');
PipelineHubspot::import();
$this->line('Importing Deals from HubSpot API...');
$this->getDealsFromHubspot();
$this->line('Found ' . count($this->importedDeals) . ' Deals to import');
if ($this->option('force')) {
$this->doImport();
} else {
if ($this->confirm('Do you want to import these deals? (yes|no)', false)) {
$this->doImport();
} else {
$this->line('Process aborted');
}
}
}
/**
* Grab Deals from Hubspot by calling the Deals API and looping through the paginated data
*
* #param int $limit: the number of deals per page
* #param string $next: the link to the next page of results
*/
private function getDealsFromHubspot(?int $limit = 100, string $next = null)
{
$endpoint = 'https://api.hubapi.com/crm/v3/objects/deals';
$properties = [
'limit' => $limit,
'properties' => implode(',', Deal::HUBSPOT_DEAL_PROPERTIES),
'hapikey' => config('hubspot.api_key'),
'associations' => 'engagements',
];
// If there's another page, append the after parameter.
if ($next) {
$properties['after'] = $next;
}
$response = Http::get($endpoint, $properties);
if ($response->successful()) {
$data = $response->json();
// If there are results, get them.
if (isset($data['results'])) {
foreach ($data['results'] as $hubspotDeal) {
$this->importedDeals[] = $hubspotDeal['properties'];
}
}
// If there's paginate we need to call the function on itself
if (isset($data['paging']['next']['link'])) {
$this->getDealsFromHubspot(null, $data['paging']['next']['after']);
}
}
$response->json();
}
/**
* Pull the Deal data in order to create a Deal model.
*
* #param array $data
*/
private function syncDeal(array $data)
{
$excludedDealStages = DealStage::excludeFromDealImport()->pluck('hubspot_id');
if ($excludedDealStages->contains($data['dealstage'])) {
return false;
}
$deal = Deal::updateOrCreate([
'hubspot_id' => $data['hs_object_id'],
], [
'name' => $data['dealname'],
'deal_stage_id' => $data['dealstage'],
'hubspot_owner_id' => $data['hubspot_owner_id'] ?? null,
]);
event(new DealImportedFromHubspot($deal));
return $deal;
}
/**
* Create and increment a nice progress bar as we import deals.
*/
private function doImport()
{
$bar = $this->output->createProgressBar(count($this->importedDeals));
$bar->start();
foreach ($this->importedDeals as $deal) {
$this->syncDeal($deal);
$bar->advance();
}
$bar->finish();
$this->newLine(2);
$this->line('Successfully imported ' . count($this->importedDeals) . ' Deals from HubSpot.');
}
}
Building on this event(new DealImportedFromHubspot($deal)); also makes an API call back to HubSpot to add the URL of the portal it had just been pulled into.
In this situation I'm thinking I either need to treat the deal importing as its own job, or add in some kind of rate limiter.
Would it be bad practise just to use sleep(10) to get around the rate limiting?
Sounds like a job for a Queue.
You can define your own rate limiter on the Queue, but the correct solution is probably to extend ShouldQueue and run $this->fail() when you get a response saying your request has been throttled.
Related
I'm saving every email I send to an entity into the database by creating a function storeEmail and make an insert of MailMessage class into EmailMessage model. Everything works fine, and the main goal is to display the message exactly as it was, when the recipient received it and retrieve all the messages I sent as a User, to a page. To be much easier to retrieve a render of each specific Message in foreach loop, I think is better to fetch it from the Model.
This is my Notification class:
class SimpleEmail extends Notification
{
use Queueable;
private $link;
private $user;
/**
* Create a new notification instance.
*
* #return void
*/
public function __construct($link)
{
$this->link = $link;
$this->user = Auth::user();
}
/**
* Get the notification's delivery channels.
*
* #param mixed $notifiable
* #return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* #param mixed $notifiable
* #return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
$mail = (new MailMessage)
->from($this->user->email, $this->user->name)
->subject('My Dummy Subject')
->greeting('To: '.$notifiable->email)
->action('Action Button', url($this->link))
->line('Thank you for reading my message')
->salutation('Friendly, '.$this->user->name);
$this->storeEmail($mail,$notifiable);
return $mail;
}
public function storeEmail($mail,$notifiable){
$email = new EmailMessage;
$email->sender_type = 'App\User';
$email->sender_id = $this->user->id;
$email->mail = $mail;
$email->save();
$notifiable->email_messages()->save($email);
}
}
Note:
I'm using Illuminate\Notifications\Messages\MailMessage
My class extends Illuminate\Notifications\Notification
I'm saving (new MailMessage) in the $email->mail = $mail;
I tried to dd($email->mail); and I get this:
^ array:20 [▼
"view" => null
"viewData" => []
"markdown" => "notifications::email"
"theme" => null
"from" => array:2 [▶]
"replyTo" => []
"cc" => []
"bcc" => []
"attachments" => []
"rawAttachments" => []
"priority" => null
"callbacks" => []
"level" => "info"
"subject" => "My Dummy Subject"
"greeting" => "To: Dohn John"
"salutation" => "Friendly, Nikolas Diakosavvas"
"introLines" => array:2 [▶]
"outroLines" => array:1 [▶]
"actionText" => "Action Button"
"actionUrl" => "http://my-example-url.com ▶"
How can I display the Mail Notification, as it was when I sent it ? What is the optimal solution for that ?
Thanks, in advance
EDITED
Managed to render MailMessage using this code works :
$email = EmailMessage::first();
return (new \App\Notifications\SimpleEmail('my-link', $email->recipient->assignto))->toMail($email->recipient);
But this is not exactly what I wanted, because every time I need to find:
Which Notification class used on every email so I can render it.
Variables for each Notification class.
In order to accomplish this:
1. You can create a accessor.
2. Use Markdown's render method.
3. Pass in render method the mail's markdown you saved in storeEmail.
You can see an example above :
use \Illuminate\Mail\Markdown;
public function getRenderAttribute(){
$markdown = new Markdown(view());
return $markdown->render($this->mail['markdown'], $this->mail);
}
I am attempting to return a list of comments related to a an entity. The query results on when it runs and returns, the related field does not provide a meaningful result.
Here is the comment entity declarations
/**
* #var Books
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Books")
*/
private $imagefk;
/**
* #var User
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Users")
*/
private $userfk;
This is my controller snippets of codes that fetches all the comment a user commented to a particular book
private function serializeComments(Comments $cmt) {
return array(
'message' => $cmt->getMessage(),
'userid' => $cmt->getUserfk(),
'bookid' => $cmt->getBookfk(),
);
}
the below function calls the function above
public function getAllCommentsAction($books)
{
$messages = $em->getRepository("AppBundle")->findBy(
array(
"imagefk" => $books
)
);
$data = array();
foreach ($messages as $message)
{
array_push($data, $this->serializeComments($message));
}
$response = new Response(json_encode($data), 200);
$response->headers->set('Content-Type', 'application/json');
return $response;
}
Here is the result of attempt
[{"message":"This is comment for a user one","userid":{"__initializer__":{},"__cloner__":{},"__isInitialized__":false},"bookid":{"path":"http:\/\/10.0.2.2:88\/xxx\/web\/uploads\/pdf\/5ub3uy8zv09cee2avi11.pdf"}}
Please how can I return the objects properties from this result instead of this
"userid":{"__initializer__":{},"__cloner__":{},"__isInitialized__":false},"bookid":{"path":"http:\/\/10.0.2.2:88\/xxx\/web\/uploads\/pdf\/5ub3uy8zv09cee2avi11.pdf"
Try accessing the object properties:
'userid' => $cmt->getUserfk()->getId(),
instead of
'userid' => $cmt->getUserfk(),
Hope this help
Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 5 years ago.
Improve this question
I have my custom Request, which extends the Backpack CrudController.
Now I would like to override the prepareForValidation of the ValidatesWhenResolvedTrait since it looks like the right place to modify my incoming data, but I can't figure out how ...
So my first question is, can I override this method? Its protected ...
protected function prepareForValidation()
And my second question, how can I modify my input on the Request or FormRreuqest objects?
Here is my RequestClass
<?php
namespace App\Http\Requests;
use App\Http\Requests\Request;
use Config;
class DonationsRequest extends \Backpack\CRUD\app\Http\Requests\CrudRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
// only allow updates if the user is logged in
return \Auth::check();
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'name' => 'required|max:255',
'email' => 'required|email',
'dob' => 'required|date',
'newsletter' => 'required|boolean',
'country' => 'sometimes|required|in:'.implode(',', Config::get('validation.countries')),
'street' => 'sometimes|required|string|max:255',
'zip' => 'sometimes|required|string|between:4,5',
'city' => 'sometimes|required|string|between:4,255',
'amount' => 'required|numeric|between:1,'.Config::get('donations.max'),
'type' => 'required|in:oo,monthly',
'provider' => 'sometimes|string|nullable',
'product_id' => 'sometimes|exists:products,id|nullable',
'campaign_id' => 'required|exists:campaigns,id',
'status' => 'sometimes|required|in:pending,canceled,success,error',
'profile' => 'sometimes|string|regex:/^profile[0-9]+$/|nullable',
];
}
/**
* Get the validation attributes that apply to the request.
*
* #return array
*/
public function attributes()
{
return [
//
];
}
/**
* Get the validation messages that apply to the request.
*
* #return array
*/
public function messages()
{
return [
//
];
}
private function prepareForValidation()
{
dd('getValidatorInstance custom');
$this->sanitizeInput();
return parent::getValidatorInstance();
}
private function sanitizeInput()
{
dd('sanitizeInput custom');
$data = $this->all();
dd($data);
// overwrite the newsletter field value to match boolean validation
$data['newsletter'] = ($data['newsletter'] == 'true' || $data['newsletter'] == '1' || $data['newsletter'] == true) ? true : false;
return $data;
}
private function validate() {
dd('validate');
}
}
As you can see, I first tried to override the getValidatorInstance method, since this looked like the common aproach to this, but it is not executed (so not overridden - protected?).
Although I didn't tried but it seems it should work you can override validationData from Illuminate\Foundation\Http\FormRequest class like.
/**
* Get data to be validated from the request.
*
* #return array
*/
protected function validationData()
{
$all = parent::validationData();
//e.g you have a field which may be json string or array
if (is_string($playerIDs = array_get($all, 'player_id')))
$playerIDs = json_decode($playerIDs, true);
$all['player_id'] = $playerIDs
return $all;
}
or you can override all method in Illuminate\Http\Concerns\InteractsWithInput trait
/**
* Get all of the input and files for the request.
*
* #return array
*/
public function all()
{
$all = parent::all();
//then do your operation
if (is_string($playerIDs = array_get($all, 'player_id')))
$playerIDs = json_decode($playerIDs, true);
$all['player_id'] = $playerIDs
return $all;
}
Could you modify the request?
$request->merge(['field' => 'new value']);
Well I am sure,this can help in modifying The input, it worked for me.[laravel 5.4]
place this
$input['url'] = $url;
$this->replace($input);
dd($input);
in listFormRequest. (use $all instead of $input, if you follow above used answer).
This only changes input,which is available even in controller. You still need to find a way to insert it into DB, or do something else to use modified input for using it in blade.
Ok I found out where the error was. I did split the Frontend Request and the Backend Request Call. Since I was working on the Backend Request the Frontend Request was not overwriting anything ... so it was my bad, no bug there, sry for the waste of time, but a big thanks to the community!
As the title states, I'm getting an odd error in Laravel 5. I'm new to Laravel, and this week I dived into Jobs/Queues. I've gotten an "Undefined Variable: $errors" error in the past, and that one I was able to understand and fix. But now, I can't seem to get past this one. To my knowledge, everything looks fine. The following breakdown will (hopefully) give you an idea of what I'm doing/where the error happens:
class PostFormFields extends Job implements SelfHandling
{
use InteractsWithQueue, SerializesModels;
/**
* The id (if any) of the Post row
*/
protected $id;
/**
* List of fields and default value for each field
*/
protected $fieldList = [
'title' => '',
'subtitle' => '',
'page_image' => '',
'content' => '',
'meta_description' => '',
'is_draft' => '8',
'publish_date' => '',
'publish_time' => '',
'layout' => 'blog.layouts.post',
'tags' => [],
];
/**
* Create a new job instance.
*
* #return void
*/
public function __construct($id = null)
{
$this->id = $id;
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
$fields = $this->fieldList;
if($this->id)
{
$fields = $this->fieldsFromModel($this->id, $fields);
} else {
$when = Carbon::now()->addHour();
$fields['publish_date'] = $when->format('M-j-Y');
$fields['publish_time'] = $when->format('g:i A');
}
/**
* Populate with old values, if they exist
* #var [type]
*/
foreach ($fields as $fieldName => $fieldValue)
{
$fields[$fieldName] = old($fieldName, $fieldValue);
}
$fields = array_merge($fields, ['allTags' => Tag::lists('tag')->all()]);
return $fields;
}
Above is the code inside the handler function of my Job class, the file it sits in is called PostFormFields.php. It's job, essentially, is just to return an array filled with all the values pertaining to a post, based on the Post Model and what's in the database that pertains to that specific Post ('title','content',etc) if a user's entered them in the past
public function create()
{
$data = $this->dispatch(new PostFormFields());
$data['title'] = 'testing';
var_dump($data);
return view('admin.post.create', $data);
}
Above is the code inside my PostController class, in the create() method. As you can tell, I'm using a resource controller for my Post Controller. It dispatches the PostFormFields Job and stores all the returned data in an array $data. However, since the create() method will be used to create a new post, only the keys should be returned, with values set to their default value ''.
This works. As you can see, i run a 'var_dump()' on the variable $data to see what, if anything, is returned. I then pass the $data array to the create View. This is where the error comes up.
Laravel "Undefined Varieble" Error
Above is a picture of the error I get when I try to access the /create route. It's clear that the $data does have the $title variable defined, as well as all the other keys in the array. Why am I getting an "Undefined Variable" array when I clearly have it defined by the time it's sent to the create View?
The line of code is says the error is in is the following:
<input type="text" class="radius" name="title" id="title" value="{{ $title }}">
You have to pass that array to view via compact function of laravel. So that you can use it in view as you want.
Please check about compact here - https://laracasts.com/discuss/channels/general-discussion/phps-compact-pros-and-cons?page=1
public function create()
{
$data = $this->dispatch(new PostFormFields());
$data['title'] = 'testing';
var_dump($data);
return view('admin.post.create', compact('data'));
}
I'm using league/fractal with JsonApiSerializer,
I've got users collection for json output.
Now I want to add some filters data to this json response (like users count for current filters).
I got this:
$resource = new Collection($dataProvider->getData(), new UserTransformer());
//the only way to include some not directly linked data i found is using setMeta():
$resource->setMetaValue('projects', $dataProvider->getProjects());
$resource->setMetaValue('somes', $dataProvider->getTasks());
But! 'projects' & 'somes' collections (yes, they are collection too) also included with 'data' key in it.
So, I've got this structure:
{
'data' => [
{//user1},{//user2},...
],
'meta' => {
'projects' => {
'data' => {...}
},
'somes' => {
'data' => {...}
}
}
}
but I want something like:
{
'data' => [
{//user1},{//user2},...
],
'meta' => {
'projects' => {...}, //there is no 'data' key
'somes' => {...} //there is no 'data' key
}
}
What should I do?
This is kinda hack but works fine without refactor Scope class which hardcoded in fractal's League\Fractal\Manager::createData() and is only way to use your own Scope class realization is to overload this method in Manager's extension.
<?php
use League\Fractal\Serializer\JsonApiSerializer;
/**
* Class EmbedSerializer
*/
class EmbedSerializer extends JsonApiSerializer
{
const RESOURCE_EMBEDDED_KEY = 'embedded';
/**
* Serialize a collection.
*
* #param string $resourceKey
* #param array $data
* #return array
*/
public function collection($resourceKey, array $data)
{
return $resourceKey === self::RESOURCE_EMBEDDED_KEY ? $data : [$resourceKey ?: 'data' => $data];
}
/**
* Serialize an item.
*
* #param string $resourceKey
* #param array $data
* #return array
*/
public function item($resourceKey, array $data)
{
return $resourceKey === self::RESOURCE_EMBEDDED_KEY ? $data : [$resourceKey ?: 'data' => [$data]];
}
}
So, now i could use it like:
/** #var $this->fractal League\Fractal\Manager */
$this->fractal->setSerializer(new EmbedSerializer());
$projectsCollection = $this->fractal->createData(
new Collection($projects, new UserProjectTransformer(), 'embedded')
)->toArray();
$resource = new Collection($users, new UserTransformer());
$resource->setMetaValue('projects', $projectsCollection);
That's all u need. Hope this will be helpful.