I have a Laravel 4 app where I need to send certain emails to users. I have no idea why but I keep getting this error on my test server (it does not happen on my local vagrant box)
Argument 1 passed to Illuminate\Mail\Mailer::getQueuedCallable() must
be of the type array, null given, called in
blablabla/vendor/laravel/framework/src/Illuminate/Mail/Mailer.php
on line 238 and defined
I tried resetting everything: database, clean clone from git. None of these worked. I also cleared my browser cache and cookies out of desperation. I am using Sync queue. So it's not even an actual queue.
Mail::send works just fine. Mail::queue throws the error above.
I literally started to pull my hair out so any help will be greatly appreciated.
Here is my code:
This is my BookingMailer class that extends Mailer class.
// partner bir arabanın rezervasyonuna onay verdiğinde müşteriye gönderilecek ödeme linki
public function sendPaymentLinkToCustomer($customer, $partner, $booking) {
$view = 'emails.bookings.request_confirmed';
$data = [
'user' => $customer->full_name // string,
'partner' => $partner->display_name // string,
'reference' => $booking->reference // integer,
'booking_id' => $booking->id // integer
];
return $this->sendTo($customer, 'Booking Request Confirmed', $view, $data, true, true);
}
And this is my Mailer class that actually queues the mail.
public function sendTo($user, $subject, $view, $data = array(), $admin_copy=false, $send_as_pm=false)
{
Mail::queue($view, $data, function ($message) use ($user, $view, $data, $subject, $admin_copy) {
$message = $message->to($user->email)->replyTo(Config::get('mail.from.address'), Config::get('mail.from.name'))->subject($subject);
return $message;
});
}
Here is the solution to this problem (at least in my case)
I was including the user first and last name to the mail. I was only showing the first letter of the last name, though. And I was using substr for a UTF-8 string, and that's why sometimes it returned problematic characters that get inside the data array and ultimately cause the queue to break.
When i used mb_substr instead of substr, my problem disappeared.
Phew, I thought I would never figure this one out on my own.
Related
Using Google API v3 I try to move a file from one folder to another. I am using a wrapper class in Laravel, the file and parent IDs are correct. Developing from the documentation, I have tried code as:
public function moveFileFromTo($fileID, $toParentID) {
$fromFile = $this->service->files->get($fileID, ['fields' => '*']);
$fromParentID = $fromFile->getParents();
$blankFile = new Google_Service_Drive_DriveFile();
$this->service->files->update($fileID, $blankFile, [
"removeParents" => $fromParentID,
"addParents" => $toParentID
]);
}
However, this seems to move the file but strips out all the meta data.
I have also tried
public function moveFileFromTo($fileID, $toParentID) {
$fromFile = $this->service->files->get($fileID, ['fields' => '*']);
$fromParentID = $fromFile->getParents();
$fromFile->setParents($fromParentID);
$this->service->files->update($fileID, $fromFile);
}
However, this gives the error:
Google\Service\Exception
{ "error": { "errors": [ { "domain": "global", "reason":
"fieldNotWritable", "message": "The resource body includes fields
which are not directly writable." } ], "code": 403, "message": "The
resource body includes fields which are not directly writable." } }
I wish to simply move the file and retain all its metadata. From the documentation, it seems either a new empty file is required in update (really weird) or I must somehow strip out the fields of the object used in the second argument ($fromFile) it does not like to be written to (even though I am simply updating the files parents - which is writable).
See also https://issuetracker.google.com/issues/199921300
Problems with Answers so far:
but grateful for responses
$fromFile = $this->service->files->get($fileID, ['fields' => 'parents, id']);
returns all ~75 attributes a lot of which are not writeable.
Instead of the expected 2 as per PHPStorm debug (note the break is at the statement immediately following the GET request so irrelevant at this point
using
unset($fromFile->shared);
still leaves other writable attributes
and indeed the file is not actually shared
UPDATE TO MY CODING
public function moveFileFromTo($fileID, $toParentID) {
$fromFile = $this->service->files->get($fileID, ["fields" => "id,parents"]);
$fromFile = $this->getParsedWritableFile($fromFile);
$fromFile->setParents($toParentID);
$this->service->files->update($fileID, $fromFile, ['addParents' => $toParentID]);
}
getParsedWritableFile is trying to just set writable attributes on a new Google Drive file object:
public function getParsedWritableFile($gdrivefile) {
$gdrivefile = new \Google_Service_Drive_DriveFile();//comment or delete, just here to check auto complete function names
$parsedFile = new \Google_Service_Drive_DriveFile();
//$parsedFile=$gdrivefile;
// currently only allow these according to https://developers.google.com/drive/api/v3/reference/files#resource-representations
$parsedFile->setName($gdrivefile->getName());//name
$parsedFile->setMimeType($gdrivefile->getMimeType());//mimeType
$parsedFile->setDescription($gdrivefile->getDescription());//description
$parsedFile->setStarred($gdrivefile->getStarred());//starred
$parsedFile->setTrashed($gdrivefile->getTrashed());//trashed
$parsedFile->setParents($gdrivefile->getParents());//parents
$parsedFile->setProperties($gdrivefile->getProperties());//properties [object]
$parsedFile->setAppProperties($gdrivefile->getAppProperties());//appProperties [object]
$parsedFile->setCreatedTime($gdrivefile->getCreatedTime());//createdTime
$parsedFile->setModifiedTime($gdrivefile->getModifiedTime());//modifiedTime
$parsedFile->setWritersCanShare($gdrivefile->getWritersCanShare());//writersCanShare
$parsedFile->setViewedByMeTime($gdrivefile->getViewedByMeTime());//viewedByMeTime
$parsedFile->setFolderColorRgb($gdrivefile->getFolderColorRgb());//folderColorRgb
$parsedFile->setOriginalFilename($gdrivefile->getOriginalFilename());//originalFilename
$parsedFile->setCopyRequiresWriterPermission($gdrivefile->getCopyRequiresWriterPermission());//copyRequiresWriterPermission
/*complex permissions*/
/*
contentHints.thumbnail.image
contentHints.thumbnail.mimeType
contentHints.indexableText
*/
$contenthints=$gdrivefile->getContentHints();//could be null meaning further functions eg getThumbnail cause exception
if($contenthints){
$parsedFile->setContentHints($contenthints->getThumbnail()->getImage());
$parsedFile->setContentHints($contenthints->getThumbnail()->getMimeType());
$parsedFile->setContentHints($contenthints->getIndexableText());
}
/*no function to get indiviual attributes*/
/*
contentRestrictions[].readOnly
ccontentRestrictions[].reason
*/
$parsedFile->setContentRestrictions($gdrivefile->getContentRestrictions());
//</ end>
return $parsedFile;
}
This is proving a bit successful but this is original meta
the above code does move it, with seemingly proper meta data, created time and EXIF data is now intact
The problem is that you are using file.update which uses HTTP PATCH methodology. By using a PATCH its going to try to update all of the properties in the file object that you send.
You did a file.get and you included fileds *
$fromFile = $this->service->files->get($fileID, ['fields' => '*']);
By including 'fields' => '*' you told the API to return to you all of the properties that the file has. A file resource has a lot of properties and they are not all writeable.
By sending the file.update method all of the fields you are telling it you want to update everything including some of the properties that you are not allowed to update. To which the api responds with fieldNotWritable
The soultion would be to do the following
$fromFile = $this->service->files->get($fileID, ['fields' => 'parents, id']);
Which will cause the method to only return the two parameters that you actual need. The id and the parents parameter. (TBH you may just need the parents part, I would need to test that.)
{
"id": "1x8-vD-XiA5Spf3qp8x2wltablGF22Lpwup8VtxNY",
"parents": ["0B1bbSFgVLpoXcVDRFRF8tTkU"
]
}
You should then be allowed to update the parent and move your file.
I don't know how to do in laravel but the problem might be
I have also faced the same problem some time and after searching internet for over months found nothing and one day Referring to documentation, seen that shared
isn't a writable field.
That's it, if you are sharing the File and trying to move the file it wouldn't move. Try to un-share the File and then try to move the File it would be done.
I ended up creating a custom function to only include writeable attributes as specified in the Google Documentation:
public function getParsedWritableFile($gdrivefile) {
$gdrivefile = new \Google_Service_Drive_DriveFile();//comment or delete, just here to check auto complete function names
$parsedFile = new \Google_Service_Drive_DriveFile();
// currently only allow these according to https://developers.google.com/drive/api/v3/reference/files#resource-representations
$parsedFile->setName($gdrivefile->getName());//name
$parsedFile->setMimeType($gdrivefile->getMimeType());//mimeType
$parsedFile->setDescription($gdrivefile->getDescription());//description
$parsedFile->setStarred($gdrivefile->getStarred());//starred
$parsedFile->setTrashed($gdrivefile->getTrashed());//trashed
$parsedFile->setParents($gdrivefile->getParents());//parents
$parsedFile->setProperties($gdrivefile->getProperties());//properties [object]
$parsedFile->setAppProperties($gdrivefile->getAppProperties());//appProperties [object]
$parsedFile->setCreatedTime($gdrivefile->getCreatedTime());//createdTime
$parsedFile->setModifiedTime($gdrivefile->getModifiedTime());//modifiedTime
$parsedFile->setWritersCanShare($gdrivefile->getWritersCanShare());//writersCanShare
$parsedFile->setViewedByMeTime($gdrivefile->getViewedByMeTime());//viewedByMeTime
$parsedFile->setFolderColorRgb($gdrivefile->getFolderColorRgb());//folderColorRgb
$parsedFile->setOriginalFilename($gdrivefile->getOriginalFilename());//originalFilename
$parsedFile->setCopyRequiresWriterPermission($gdrivefile->getCopyRequiresWriterPermission());//copyRequiresWriterPermission
/*complex permissions*/
/*
contentHints.thumbnail.image
contentHints.thumbnail.mimeType
contentHints.indexableText
*/
$contenthints=$gdrivefile->getContentHints();//could be null meaning further functions eg getThumbnail cause exception
if($contenthints){
$parsedFile->setContentHints($contenthints->getThumbnail()->getImage());
$parsedFile->setContentHints($contenthints->getThumbnail()->getMimeType());
$parsedFile->setContentHints($contenthints->getIndexableText());
}
/*no function to get indiviual attributes*/
/*
contentRestrictions[].readOnly
ccontentRestrictions[].reason
*/
$parsedFile->setContentRestrictions($gdrivefile->getContentRestrictions());
return $parsedFile;
}
and called with
public function moveFileFromTo($fileID, $toParentID) {
$fromFile = $this->service->files->get($fileID, ["fields" => "id,parents"]);
$fromFile = $this->getParsedWritableFile($fromFile);
$fromFile->setParents($toParentID);
$this->service->files->update($fileID, $fromFile, ['addParents' => $toParentID]);
}
I'm using hostfact https://www.hostfact.nl for our billing. Now I want to extend the code and get a loop on executing the internalAPI in hooks.php. I tried to find an answer in the documentation https://www.hostfact.nl/developer/api/ but without success.
// When a client is edited, the bankParameters are checked and validated.
// That is only an example
function action_debtor_is_edited($parameters){
$params = array(
'DebtorCode' => $parameters['DebtorCode']
);
$hostfactResult = internalAPI('debtor', 'show', $params);
$bankParams = array(
'Identifier' => '2',
'AccountNumber' => 'AT6734080000000000',
'AccountBank' => 'test bank',
'AccountCity' => 'city',
'AccountBIC' => 'RLNODASD'
);
$bankResult = internalAPI('debtor', 'edit', $bankParams);
}
I've already asked the support and got follwoing answer:
One of the option is keeping track of the situation. For example, use
a static or global variable. When you call an (internal)API call from
a webhook, first flag that variable, so in the newly called webhook
you know that is not needed to execute the webhook again (at least not
the part which causes the loop). In the original call, after receiving
the result, the flag can be unset again.
My Problem is, I'm new to PHP and don't understand what I should do to avoid the loop. The $bankParams variable is intentionally hardcoded.
Can you help?
I write test for Laravel app with codeception and modules Laravel5, REST.
One of api test:
public function testEmailRegistration(ApiTester $I) {
...
// Not correct data
$I->sendPOST($route, [
'first_name' => (string)$this->faker->randomNumber(),
'password' => $this->faker->password(1, 7),
'email' => 'not_valid_email',
]);
$I->seeResponseCodeIs(HttpCode::UNPROCESSABLE_ENTITY);
// Correct data
\Illuminate\Support\Facades\Queue::fake();
$I->sendPOST($route, [
'first_name' => $firstName,
'password' => $password,
'email' => $email,
]);
\Illuminate\Support\Facades\Queue::assertPushed(\App\Jobs\SendEmail::class);
...
}
I send requests on incorrect and correct data and make some assertions. In addition I check, that job is present in queue.
After execute test I give error:
[Error] Call to undefined method Illuminate\Queue\SyncQueue::assertPushed()
After Queue:fake facade \Illuminate\Support\Facades\Queue must resolves to QueueFake, but in fact is still QueueManager, thus assertPushed function is undefined.
Execution of $I->sendPOST() reset call Queue::fake. It happened in laravel 5 module \Codeception\Lib\Connector\Laravel5, method doRequest.
protected function doRequest($request)
{
if (!$this->firstRequest) {
$this->initialize($request);
}
$this->firstRequest = false;
$this->applyBindings();
$this->applyContextualBindings();
$this->applyInstances();
$this->applyApplicationHandlers();
$request = Request::createFromBase($request);
$response = $this->kernel->handle($request);
$this->app->make('Illuminate\Contracts\Http\Kernel')->terminate($request, $response);
return $response;
}
Each call of doRequest except the first init app again and some configurations as Queue::fake are cleared.
One of decision is one request per test. Is there another variant to work Queue::fake when in test make more then one request?
I am not sure why the Laravel module does this but I found a work-around that allows you to use fakes:
public function someTest(ApiTester $I): void
{
// what the SomeFacade::fake method call does is basically create
// a fake object and swaps it for the original implementation in
// the app container, so the we're recreating that behavior here
// only this will be persisted even after the request is issued:
$notification_fake = new NotificationFake();
// `haveInstance` is a method from Laravel Codeception Module
// which sets an object in the app container for you:
$I->haveInstance(ChannelManager::class, $notification_fake);
// making the request
$I->sendPUT('some url', $some_payload);
// assertions
$I->canSeeResponseCodeIs(Response::HTTP_OK);
$notification_fake->assertSentToTimes($expected_user, MyNotification::class, 1);
}
note that this test method is only for illustrative purposes and it misses so of the details, hence the undefined variables and such.
Also note that I user the notification fake which get registered at Illuminate\Notifications\ChannelManager, unlike most fakes that you can register under their aliased name e.g. queue. So you have to check what is being instantiated and how to swap it on your own. You can either find this in respective service providers for each service. Most of the time it's lowercase name of the facade.
I am writing this simple code to send a mail with attachment, however, I am not able to pass the path to the file variable.
$pathToFile = "Sale-".$id.".csv";
Mail::send(array('html' => 'sales.invoice_template'), $data, function($message)
{
$message->to('test#test.com'); // dummy email
$message->attach($pathToFile);
});
The above code throws:
Undefined variable: pathToFile
Also, I tried passing a variable (added $pathToVariable with $message in above closure) to the closure but it throws following error:
Missing argument 2 for SaleController::{closure}()
It basically doesn't identify any variable outside the closure. Can anyone please help me out here?
You can try this:
$pathToFile = "Sale-".$id.".csv";
Mail::send(array('html' => 'sales.invoice_template'), $data, function($message) use ($pathToFile)
{
$message->to('test#test.com'); // dummy email
$message->attach($pathToFile);
});
The instruction:
use ($pathToFile)
...allows you to use your variable in the closure.
When referencing $pathToFile within your closure, the script is looking for $pathToFile to be declared within the closure. As no declaration exists, you see the undefined variable error.
Any variable used inside a function is by default limited to the local function scope.
Source: http://www.php.net/manual/en/language.variables.scope.php
To fix it you should be able to pass $pathToFile into your closure, e.g.:
Mail::send(array('html' => 'sales.invoice_template'), $data, function($message, $pathToFile)
{
$message->to('test#test.com'); // dummy email
$message->attach($pathToFile);
});
I have a controller which I use for a login form. In the view, I have a {error} variable which I want to fill in by using the parser lib, when there is an error. I have a function index() in my controller, controlled by array $init which sets some base variables and the error message to '':
function index()
{
$init = array(
'base_url' => base_url(),
'title' => 'Login',
'error' => ''
);
$this->parser->parse('include/header', $init);
$this->parser->parse('login/index', $init);
$this->parser->parse('include/footer', $init);
}
At the end of my login script, I have the following:
if { // query successful }
else
{
$init['error'] = "fail";
$this->parser->parse('login/index', $init);
}
Now, of course this doesn't work. First of all, it only loads the index view, without header and footer, and it fails at setting the original $init['error'] to (in this case) "fail". I was trying to just call $this->index() with perhaps the array as argument, but I can't seem to figure out how I can pass a new $init['error'] which overrides the original one. Actually, while typing this, it seems to impossible to do what I want to do, as the original value will always override anything new.. since I declare it as nothing ('').
So, is there a way to get my error message in there, or not? And if so, how. If not, how would I go about getting my error message in the right spot? (my view: {error}. I've tried stuff with 'global' to bypass the variable scope but alas, this failed. Thanks a lot in advance.
$init musst be modified before generating your view.
To load your header and footer you can include the following command and the footer's equivalent into your view.
<?php $this->load->view('_header'); ?>
to display errors, you can as well use validation_errors()
if you are using the codeigniter form validation.
if you are using the datamapper orm for codeigniter you can write model validations, and if a query fails due to validation rule violation, you get a proper error message in the ->error property of your model.
Code for your model:
var $validation = array(
'user_name' => array(
'rules' => array('required', 'max_length' => 120),
'label' => 'Name'
)
);
You might try this:
function index() {
$init = array(
'base_url' => base_url(),
'title' => 'Login',
'error' => ''
);
$string = $this->parser->parse('include/header', $init, TRUE);
$string .= $this->parser->parse('login/index', $init, TRUE);
$string .= $this->parser->parse('include/footer', $init, TRUE);
$this->parser->parse_string(string);
}
In parse()you can pass TRUE (boolean) to the third parameter, when you want data returned instead of being sent (immediately) to the output class. By the other hand, the method parse_string works exactly like `parse(), only accepts a string as the first parameter in place of a view file, thus it works in conjunction.