I have an application which has been running successfully for over a year on both the production server and my development machine (both running IIS without any pretty URLs). Yesterday I moved the latest in a series of (until now successful) updates from development to production, and since then many of the routes have been failing on the production server, even though they work fine on dev. I've verified that the exact same application and route files are present on both machines, I've confirmed that php artisan routes produce the same output on both machines, I've confirmed that their autoload_classmap.php files are identical, and I've verified that both servers are running Laravel 4.1.23 .
After some testing it appears that, for example, the URL https://localhost/EPHY/index.php/child/3958/edit, which triggers the ChildController::edit() RESTful method on dev, triggers ChildController::create() on production, which then crashes because it can't find the familyId parameter it's expecting. Here's the applicable line in routes.php on both servers:
Route::resource( 'child', 'ChildController', array(
'create' => 'child.create',
'store' => 'child.store',
'show' => 'child.show',
'edit' => 'child.edit',
'update' => 'child.update',
'destroy' => 'child.destroy'
));
I should note that Family, another RESTful resource, works successfully on both servers, but others don't. After doing some more testing, it actually seems like any routes relating to models that have an Eloquent relationship with Family are failing, and all the others are working fine. That's just weird...
Can anybody suggest what may be causing this, or at least where I can look? I'm happy to post additional information.
UPDATE: I continue to poke at this problem, and it's becoming apparent that this is definitely only affecting routes related to models with an Eloquent relationship to the Family model; all other routes in the application appear to be working correctly.
Ok, I figured this out, and it's lame. As I noted above, this was only affecting models which had an Eloquent relationship with Family. What that means in practice is that all of the ::create() methods of the associated Controllers needed the Family::id value. So, here's the code I was checking the determine whether I needed that value from Input::get() or Input::old():
$familyId = ( empty( Input::old() ) )? Input::get('familyId') : Input::old('family_id');
Anybody see the problem? empty() can't take a function call as a parameter. Everything worked fine as soon as I changed the code to:
$familyId = ( Input::old() )? Input::old('family_id') : Input::get('familyId') ;
Now, how did I figure this out? Well, after spending a day and a half assuming I knew what "Can't use function return value in write context" meant, I FINALLY CHECKED THE DAMN ERROR MESSAGE ON GOOGLE! Let this be a lesson to the newbs and a reminder to the veterans, never assume you understand what an error means without double-checking with the groupmind...
I'm so ashamed...
Related
I'm using Laravel for my project and I'm new to unit/feature testing so I was wondering what is the best way to approach more complicated feature cases when writing tests?
Let's take this test example:
// tests/Feature/UserConnectionsTest.php
public function testSucceedIfConnectAuthorised()
{
$connection = factory(Connection::class)->make([
'sender_id' => 1,
'receiver_id' => 2,
'accepted' => false,
'connection_id' => 5,
]);
$user = factory(User::class)->make([
'id' => 1,
]);
$response = $this->actingAs($user)->post(
'/app/connection-request/accept',
[
'accept' => true,
'request_id' => $connection->id,
]
);
$response->assertLocation('/')->assertStatus(200);
}
So we've got this situation where we have some connection system between two users. There is a Connection entry in the DB created by one of the users. Now to make it a successful connection the second user has to approve it. The problem is within the UserController accepting this through connectionRequest:
// app/Http/Controllers/Frontend/UserController.php
public function connectionRequest(Request $request)
{
// we check if the user isn't trying to accept the connection
// that he initiated himself
$connection = $this->repository->GetConnectionById($request->get('request_id'));
$receiver_id = $connection->receiver_id;
$current_user_id = auth()->user()->id;
if ($receiver_id !== $current_user_id) {
abort(403);
}
[...]
}
// app/Http/Repositories/Frontend/UserRepository.php
public function GetConnectionById($id)
{
return Connection::where('id', $id)->first();
}
So we've got this fake (factory created) connection in the test function and then we unfortunately are using its fake id to run a check within the real DB among real connections, which is not what we want :(
Researching I found an idea of creating interfaces so then we can provide a different method bodies depending if we're testing or not. Like here for GetConnectionById() making it easy to fake answers to for the testing case. And that seems OK, but:
for one it looks like a kind of overhead, besides writing tests I have to make the "real" code more complicated itself for the sole purpose of testing.
and second thing, I read all that Laravel documentation has to say about testing, and there is no one place where they mention using of interfaces, so that makes me wonder too if that's the only way and the best way of solving this problem.
I will try to help you, when someone start with testing it is not easy at all, specially if you don't have a strong framework (or even a framework at all).
So, let me try help you:
It is really important to differentiate Unit testing vs Feature testing. You are correctly using Feature test, because you want to test business logic instead of a class directly.
When you test, my personal recommendation is always create a second DB to only use with tests. It must be completely empty all the time.
So, for you to achieve this, you have to define the correct environment variables in phpunit.xml, so you don't have to do magic for this to work when you only run tests.
Also, use RefreshDatabase trait. So, each time you run a test, it is going to delete everything, migrate your tables again and run the test.
You should always create what you need to have as mandatory for your test to run. For example, if you are testing if a user can cancel an order he/she created, you only need to have a product, a user and an invoice associated with the product and user. You do not need to have notifications created or anything not related to this. You must have what you expect to have in the real case scenario, but nothing extra, so you can truly test that it fully works with the minimum stuff.
You can run seeders if your setup is "big", so you should be using setup method.
Remember to NEVER mock core code, like request or controllers or anything similar. If you are mocking any of these, you are doing something wrong. (You will learn this with experience, once you truly know how to test).
When you write tests names, remember to never use if and must and similar wording, instead use when and should. For example, your test testSucceedIfConnectAuthorised should be named testShouldSucceedWhenConnectAuthorised.
This tip is super personal: do not use RepositoryPattern in Laravel, it is an anti-pattern. It is not the worst thing to use, but I recommend having a Service class (do not confuse with a Service Provider, the class I mean is a normal class, it is still called Service) to achieve what you want. But still, you can google about this and Laravel and you will see everyone discourages this pattern in Laravel.
One last tip, Connection::where('id', $id)->first() is exactly the same as Connection::find($id).
I forgot to add that, you should always hardcode your URLs (like you did in your test) because if you rely on route('url.name') and the name matches but the real URL is /api/asdasdasd, you will never test that the URL is the one you want. So congrats there ! A lot of people do not do this and that is wrong.
So, to help you in your case, I will assume you have a clear database (database without tables, RefreshDatabase trait will handle this for you).
I would have your first test as this:
public function testShouldSucceedWhenConnectAuthorised()
{
/**
* I have no idea how your relations are, but I hope
* you get the main idea with this. Just create what
* you should expect to have when you have this
* test case
*/
$connection = factory(Connection::class)->create([
'sender_id' => factory(Sender::class)->create()->id,
'receiver_id' => factory(Reciever::class)->create()->id,
'accepted' => false,
'connection_id' => factory(Connection::class)->create()->id,
]);
$response = $this->actingAs(factory(User::class)->create())
->post(
'/app/connection-request/accept',
[
'accept' => true,
'request_id' => $connection->id
]
);
$response->assertLocation('/')
->assertOk();
}
Then, you should not change anything except phpunit.xml environment variables pointing to your testing DB (locally) and it should work without you changing anything in your code.
I have a project with a fairly standard Dev-on-Homestead to Staging to Production workflow. All are using Laravel 7.2.2/Ubuntu 18.04/PHP 7.4.3
This cropped up today with some form requests.
Symfony\Component\ErrorHandler\Error\FatalError
Type of App\Http\Requests\CreateHighlight::$errorBag must be string (as in class Illuminate\Foundation\Http\FormRequest)
This is a named $errorBag that we've been using since 5.4 or so?
I changed the $errorBag to protected string $errorBag='highlightCreate'; and proceeded to test and deploy.
That same code kicked this back from the staging error logs:
staging.ERROR: Type of App\Http\Requests\CreateHighlight::$errorBag must not be defined (as in class Illuminate\Foundation\Http\FormRequest)
I have checked and rechecked and checked a fourth, fifth, sixth time. Everything appears to be identical between the two environments, yet I cannot for the life of me understand why this is happening. They're the same error except they're contradicting each other.
Based on FormRequest it clearly seems like errorBag is not defined as a string. This clearly seems like it is something with your local file there is wrong. Never the less the errorBag should not be defined as a string.
Hello guys,
I'm making an API using Laravel. In one of my scripts, I make an update on a field, like this :
user::where('uuid', $uuid)->update(['date' => $date]);
I noticed that the primary key increments when doing this. My obvious conclusion is that Eloquent makes a delete - insert in place of a regular MySQL update.
And so the question is, why ?
Thanks ahead.
It's not possible that this line of code will update id your records. Whenever you thing something strange happens in your application (not only in Laravel), you should:
analyse what exactly code is running that causes this problem (for example you think the error is in this line but you execute also some other custom function where error might occur)
verify if there are no extra framework dependant code launched - in this case events for user model
verify if there are no triggers in Database (that will automatically update/insert/delete records)
I am having an issue promoting an unpublished page post via the ads-api.
This was previously working ok for me, but began causing problems yesterday.
I first create an ad_campaign, and then, using the returned campaign_id, I attempt to create an adgroup.
The response from the server is
array(1) {
'error' =>
array(3) {
'message' =>
string(53) "(#100) Invalid parameter: adgroup_spec["campaign_id"]"
'type' =>
string(14) "OAuthException"
'code' => int(100)
}
}
I have verified that I am sending over the correct, newly created campaign_id.
Another point of interest; when I use the UI to delete the ad_campaign afterwards, I am told that I do not have permission to do so. User XXXX does not have permission to access campaign YYYY.
I'm thinking this must be an access_token/permission issue but I'm stumped. The ad objects are created using the user's token, and the unpublished page post is created using the page's access token.
NB: I can provide snippets if needs be, but I'm fairly sure this is a problem with the object creation flow as opposed to a code issue.
Has anyone seen anything similar?
Cheers, Gary
Update with POST data
array(7) {
'campaign_id' =>
int(6013621027457)
'bid_type' =>
int(6)
'bid_info' =>
string(37) "{"clicks":10,"reach":10,"actions":80}"
'conversion_specs' =>
string(66) "{"action.type":"offsite_conversion","offsite_pixel":6013619180457}"
'creative' =>
string(86) "{"type":27,"object_id":407012979370770,"auto_update":false,"story_id":565852233486843}"
'name' =>
string(23) "PropelAd (via PropelAd)"
'targeting' =>
string(95) "{"countries":["IE","AE","GB"],"friends_of_connections":[407012979370770],"page_types":["feed"]}"
}
Yes, it happens to me as well from time to time when uploading ads.
It's very statistical, and it seems to be an off-sync between Facebook servers (the campaign was already successfully uploaded, but the ad-request-handler does not recognize that id).
Wait a few seconds, and try again - after a few shots, it will always work (usually there's no problem, it's pretty rare but happens - and never lasted more than a minute of not-recognizing).
Perhaps it happened more today, due to the general Facebook failures.
I can only assume/hope that Facebook keep track of these errors, and are working on minimizing them by syncing their servers better.
Yep - we are currently putting a lot of time in checking why do ad group fail on unpublished posts - specifically on posts scheduled in the future.
The behavior is very inconsistent. The errors we get are slightly different: "Could not save ad", "Invalid ad creative". The errors are not reported on all ad groups but mostly on one of the ad groups of a whole batch.
We do make sure that the campaign start time is equal or AFTER the time the post is scheduled to.
When we publish the post now and test with the same campaign structure it succeeds (although this also succeeds sometimes on the second try - like it was mentioned here, that the campaign takes some time maybe to become fully valid)
A related bug I opened is here:
https://developers.facebook.com/bugs/593878450648811?browse=external_tasks_search_results_52662e53c59bd2e63625449
I'm using Basset 4 to manage assets.
In the config file i'm declaring a collection 'admin'
return array(
'collections' => array(
'admin' => function($collection)
{
$collection->directory('assets/js', function($collection)
{
$collection->add('vendor/jquery-1.9.1.min.js');
});
},
),
...
)
later in a view, I would like to add an extra file in admin collection.
I've try the following code, but it doesn't work:
Basset::collection('admin', function($collection)
{
$collection->add('function.js');
});
Is there a way to add file into a collection from a view or from a controller?
Thank you
Basset isn't really designed to work like that. You should be defining all your assets within that initial call, even though the ability to add assets throughout the execution of an application is possible, it's not recommended.
When building a collection assets that are added for a particular route won't be available to the builder since Artisan doesn't fire any routes, etc.
Adjusting a collection in numerous places can often lead to confusion further down the line.
I know this isn't ideal as you're probably looking at implementing page specific JavaScript, correct? I've thought about it but can't really think of a clean solution (suggestions?), although I've heard of people assigning a unique ID to the body or perhaps some classes that their JavaScript can then attach themselves to.
It's not brilliant but that's the best I can give you at the moment.