phpunit, laravel: Cannot use "parent" when current class scope has no parent - php

I'm using PHPUnit 6.5.13 and Laravel 5.5 on PHP 7.4. I recently upgraded from PHP 7.2 to 7.4. and it seems like that triggered the error.
In my test I use $this->expectsEvents in order to test that an event is fired. The test class looks a little like this:
namespace Tests\Feature;
use Tests\TestCase;
use App\Events\OrderReSent;
class MyEventTest extends TestCase {
/** #test */
public function authenticated_client_can_resend()
{
$this->expectsEvents(OrderReSent::class); // there is some more code but this is the line that returns the error
}
}
OrderReSent looks like this (I've tried commenting out broadcastOn and remove InteractsWithSockets use, no change in result):
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class OrderReSent
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $invoiceId;
public function __construct($invoiceId)
{
$this->invoiceId = $invoiceId;
}
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}
The only place I see parent::__construct being called is in Illuminate\Broadcasting\PrivateChannel, which extends Illuminate\Broadcasting\Channel (and it is a child class, so I don't understand why it would throw this error):
namespace Illuminate\Broadcasting;
class PrivateChannel extends Channel
{
/**
* Create a new channel instance.
*
* #param string $name
* #return void
*/
public function __construct($name)
{
parent::__construct('private-'.$name);
}
}
The stacktrace looks like this and makes me believe Mockery is the culprit:
1) Tests\Feature\MyEventTest::authenticated_client_can_resend
ErrorException: Cannot use "parent" when current class scope has no parent
/project-root/vendor/mockery/mockery/library/Mockery/Loader/EvalLoader.php:16
/project-root/vendor/mockery/mockery/library/Mockery/Loader/EvalLoader.php:16
/project-root/vendor/mockery/mockery/library/Mockery/Container.php:219
/project-root/vendor/mockery/mockery/library/Mockery.php:89
/project-root/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MocksApplicationServices.php:99
/project-root/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MocksApplicationServices.php:54
/project-root/tests/Feature/MyEventTest.php:29

I had same issue - it turned out that mockery/mockery was set to version 0.9 in my composer.json. Upgrading mockery/mockery to version 1.3 solved the problem for me.
Related composer.json fragment:
"mockery/mockery": "~1.3.0",
"phpunit/phpunit": "~8.0",
Try setting same versions and run composer update

The likely culprit is due to new syntax requirements for setUp and tearDown.
Source
It can be caused by a missing return type, which is void.
For example, change this:
public function setUp()
{
parent::setUp();
}
public function tearDown()
{
parent::tearDown();
}
to this:
public function setUp() : void
{
parent::setUp();
}
public function tearDown() : void
{
parent::tearDown();
}
Note: I found this question looking for the error message in a unit test, and oddly enough it was related to m::close(), so I'm describing a different problem than the original question, but my answer will be relevant.

Related

How should one implement a custom Doctrine purger when using Symfony 6?

The Symfony docs shows a solution, but it doesn't appear to work (i.e. Doctrine\Bundle\FixturesBundle\Purger\PurgerFactory needs to be replaced with Doctrine\Bundle\FixturesBundle\Purger\ORMPurgerFactory, and other changes). I modified the code as shown below, but am pretty certain I am not doing it correctly.
<?php
declare(strict_types=1);
namespace App\DataFixtures\Purger;
use Doctrine\Bundle\FixturesBundle\Purger\PurgerFactory;
use Doctrine\Common\DataFixtures\Purger\PurgerInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Bundle\FixturesBundle\Purger\ORMPurgerFactory;
class CustomPurgerFactory implements PurgerFactory
{
public function __construct(private ORMPurgerFactory $purgeFactory)
{
}
public function createForEntityManager(?string $emName, EntityManagerInterface $em, array $excluded = [], bool $purgeWithTruncate = false) : PurgerInterface
{
// Change $excluded, $purgeWithTruncate as desired.
return new CustomPurger($emName, $em, $excluded, $purgeWithTruncate, $this->purgeFactory);
}
}
<?php
declare(strict_types=1);
namespace App\DataFixtures\Purger;
use Doctrine\Common\DataFixtures\Purger\PurgerInterface;
use Doctrine\Common\DataFixtures\Purger\ORMPurgerInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Bundle\FixturesBundle\Purger\ORMPurgerFactory;
class CustomPurger implements ORMPurgerInterface
{
public function __construct(private ?string $emName, private EntityManagerInterface $entityManager, private array $excluded, private bool $purgeWithTruncate, private ORMPurgerFactory $purgeFactory)
{
}
public function setEntityManager(EntityManagerInterface $entityManager):void
{
// Seems rather redundent doing this even though I earlier inject $entityManager.
$this->entityManager = $entityManager;
}
public function purge() : void
{
// Delete any tables which must be deleted first to prevent FK constraint errors.
// This doesn't seem write.
$purger = $this->purgeFactory->createForEntityManager($this->emName, $this->entityManager, $this->excluded, $this->purgeWithTruncate);
$purger->purge();
}
}
services:
App\DataFixtures\Purger\DoctrinePurgerFactory:
tags:
- { name: 'doctrine.fixtures.purger_factory', alias: 'my_purger' }
arguments:
- '#doctrine.fixtures.purger.orm_purger_factory'
Or should it be done by decorating the default purger as suggested by this post?
Okay. So you do have a few things wrong and the docs are somewhat out of date. From a big picture point of view you want something like:
bin/console doctrine:fixtures:load --purger=my_purger
to use your custom purger factory (aliased as my_purger) to instantiate and execute your custom purger's purge method. The job of the factory is to just create the purger not to execute it.
I followed the docs and implemented PurgerInterface but the purge command complained about it not implementing ORMPurgerInterface which, as you noted, adds a seemingly superfluous method. I think it is still a work in progress. The default ORMPurger has a couple of additional public methods not defined in any interface which is also strange. The fact that Doctrine is inconsistent with it's usage of the Interface suffix does not help. But it is what it is.
This works under 6.1:
# CustomPurger.php
use Doctrine\Common\DataFixtures\Purger\ORMPurgerInterface;
use Doctrine\ORM\EntityManagerInterface;
class CustomPurger implements ORMPurgerInterface
{
private EntityManagerInterface $em;
public function setEntityManager(EntityManagerInterface $em) : void
{
$this->em = $em;
}
public function purge() : void
{
dd(' my purger');
}
}
# CustomPurgerFactory.php
use Doctrine\Bundle\FixturesBundle\Purger\PurgerFactory;
use Doctrine\Common\DataFixtures\Purger\PurgerInterface;
use Doctrine\ORM\EntityManagerInterface;
class CustomPurgerFactory implements PurgerFactory
{
public function createForEntityManager(?string $emName, EntityManagerInterface $em, array $excluded = [], bool $purgeWithTruncate = false) : PurgerInterface
{
return new CustomPurger($em);
}
}
# services.yaml
App\Purger\CustomPurgerFactory:
tags:
- { name: 'doctrine.fixtures.purger_factory', alias: 'my_purger' }
bin/console doctrine:fixtures:load --purger=my_purger
> purging database
^ " my purger"
As far as decorating goes, you decorate a service when you want to modify some methods without extending the original class. There is only one method here and it's quite a doozy so I don't think decorating will help.
If you wanted to always use your purger without the --purger option then you could probably point the default purger factory service id to your factory. I'll leave that as an exercise for the reader.
One final note: I took a look at your decorating link. Don't know what they were trying to do but I do know it has nothing to do with decorating.

New alternative for getDoctrine() in Symfony 5.4 and up

As my IDE points out, the AbstractController::getDoctrine() method is now deprecated.
I haven't found any reference for this deprecation neither in the official documentation nor in the Github changelog.
What is the new alternative or workaround for this shortcut?
As mentioned here:
Instead of using those shortcuts, inject the related services in the constructor or the controller methods.
You need to use dependency injection.
For a given controller, simply inject ManagerRegistry on the controller's constructor.
use Doctrine\Persistence\ManagerRegistry;
class SomeController {
public function __construct(private ManagerRegistry $doctrine) {}
public function someAction(Request $request) {
// access Doctrine
$this->doctrine;
}
}
You can use EntityManagerInterface $entityManager:
public function delete(Request $request, Test $test, EntityManagerInterface $entityManager): Response
{
if ($this->isCsrfTokenValid('delete'.$test->getId(), $request->request->get('_token'))) {
$entityManager->remove($test);
$entityManager->flush();
}
return $this->redirectToRoute('test_index', [], Response::HTTP_SEE_OTHER);
}
As per the answer of #yivi and as mentionned in the documentation, you can also follow the example below by injecting Doctrine\Persistence\ManagerRegistry directly in the method you want:
// src/Controller/ProductController.php
namespace App\Controller;
// ...
use App\Entity\Product;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Response;
class ProductController extends AbstractController
{
/**
* #Route("/product", name="create_product")
*/
public function createProduct(ManagerRegistry $doctrine): Response
{
$entityManager = $doctrine->getManager();
$product = new Product();
$product->setName('Keyboard');
$product->setPrice(1999);
$product->setDescription('Ergonomic and stylish!');
// tell Doctrine you want to (eventually) save the Product (no queries yet)
$entityManager->persist($product);
// actually executes the queries (i.e. the INSERT query)
$entityManager->flush();
return new Response('Saved new product with id '.$product->getId());
}
}
Add code in controller, and not change logic the controller
<?php
//...
use Doctrine\Persistence\ManagerRegistry;
//...
class AlsoController extends AbstractController
{
public static function getSubscribedServices(): array
{
return array_merge(parent::getSubscribedServices(), [
'doctrine' => '?'.ManagerRegistry::class,
]);
}
protected function getDoctrine(): ManagerRegistry
{
if (!$this->container->has('doctrine')) {
throw new \LogicException('The DoctrineBundle is not registered in your application. Try running "composer require symfony/orm-pack".');
}
return $this->container->get('doctrine');
}
...
}
read more https://symfony.com/doc/current/service_container/service_subscribers_locators.html#including-services
In my case, relying on constructor- or method-based autowiring is not flexible enough.
I have a trait used by a number of Controllers that define their own autowiring. The trait provides a method that fetches some numbers from the database. I didn't want to tightly couple the trait's functionality with the controller's autowiring setup.
I created yet another trait that I can include anywhere I need to get access to Doctrine. The bonus part? It's still a legit autowiring approach:
<?php
namespace App\Controller;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Persistence\ObjectManager;
use Symfony\Contracts\Service\Attribute\Required;
trait EntityManagerTrait
{
protected readonly ManagerRegistry $managerRegistry;
#[Required]
public function setManagerRegistry(ManagerRegistry $managerRegistry): void
{
// #phpstan-ignore-next-line PHPStan complains that the readonly property is assigned outside of the constructor.
$this->managerRegistry = $managerRegistry;
}
protected function getDoctrine(?string $name = null, ?string $forClass = null): ObjectManager
{
if ($forClass) {
return $this->managerRegistry->getManagerForClass($forClass);
}
return $this->managerRegistry->getManager($name);
}
}
and then
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use App\Entity\Foobar;
class SomeController extends AbstractController
{
use EntityManagerTrait
public function someAction()
{
$result = $this->getDoctrine()->getRepository(Foobar::class)->doSomething();
// ...
}
}
If you have multiple managers like I do, you can use the getDoctrine() arguments to fetch the right one too.

Laravel Job fails during execution: method_exists()

We are running on Laravel 6 and we have got the following problem. A job we execute, that counts the number of impressions and clics of certain images triggers the following error, due to a high number of calls to the function:
method_exists(): The script tried to execute a method or access a
property of an incomplete object. Please ensure that the class
definition "App\Jobs\RegisterHouseLog" of the object you are trying to
operate on was loaded before unserialize() gets called or provide an
autoloader to load the class definition
We already increased the number of tries so it executes after sometime, so it's not the actual problem, but it sends an error to our error logs (Slack Channel) and causes a lot of "spam".
I was trying to fix the above error but I wasn't able to fix it, so at least I tried to "mute" the notification through an "failed job exception" but still to it fails.
The best would be to resolve the actual problem, the second best would be to mute it.
Anyone could help?
The Job:
<?php
namespace App\Jobs;
use App\HouseLog;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Exception;
class RegisterHouseLog implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 20;
public $house_id;
public $user_id;
public $date;
public $type;
/**
* Create a new job instance.
*
* #return void
*/
public function __construct($house_id,$user_id,$type, $date)
{
$this->house_id = $house_id;
$this->user_id = $user_id;
$this->type = $type;
$this->date = $date;
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
$log = new HouseLog();
$log->user_id = $this->user_id;
$log->house_id = $this->house_id;
$log->type = $this->type;
$log->date = $this->date;
$log->save();
}
public function failed(Exception $exception)
{
Log::critical('Failed Register House');
}
}
And the call:
<?php
namespace App\Http\Controllers\api;
use App\HouseLog;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Jobs\RegisterHouseLog;
use Carbon\Carbon;
class HouseLogController extends Controller
{
public function registerLog(Request $request)
{
$date = Carbon::now();
RegisterHouseLog::dispatch($request->house_id, $request->user_id, $request->type, $date);
}
}
Thanks a lot!!
This error is likely due to the job dispatcher not being able to resolve \App\Jobs\RegisterHouseLog when it pulls from the job queue to kick off a job.
Try clearing the class loader cache:
artisan cache:clear
Also try restarting your job dispatcher process.
artisan queue:restart
It may not be the best solution, but you could also fix this by removing implements ShouldQueue from your job class definition; it would make the job kick off right away without going through the queue.

Factory helper not working within PHPUnit setup method

Writing some unit tests, and I want to have an object created before the tests in the class are done. So I set up the setUpBeforeClass() method:
<?php
namespace Tests\Unit;
use Tests\TestCase;
use App\Location;
class UserTests extends TestCase {
const FAKEID = 9999999;
public static function setUpBeforeClass() : void {
parent::setUpBeforeClass();
factory(Location::class)->make(["id" => self::FAKEID])->save();
}
}
But when I try running this, I get this error:
InvalidArgumentException: Unable to locate factory with name [default] [App\Location].
But the factory class is set up properly. In fact, if I move this same line down to one of my test functions it works perfectly.
public function testCreateUser() {
factory(Location::class)->make(["id" => self::FAKEID])->save();
// do other stuff...
}
The only thing that sticks out to me as different about setUpBeforeClass() is that it's a static method, but I don't know why that would prevent the factory class from working.
Laravel does a lot of setting up in the setUp() method in the TestCase class. The setUpBeforeClass() method is called before that, that's why your factory is not loaded yet.
The Laravel's TestCase class setup method (see class):
/**
* Setup the test environment.
*
* #return void
*/
protected function setUp()
{
if (! $this->app) {
$this->refreshApplication();
}
$this->setUpTraits();
foreach ($this->afterApplicationCreatedCallbacks as $callback) {
call_user_func($callback);
}
Facade::clearResolvedInstances();
Model::setEventDispatcher($this->app['events']);
$this->setUpHasRun = true;
}
Change your code to use setUp instead:
protected static function setUp() : void
{
parent::setUp();
factory( Location::class )->make( ["id" => self::FAKEID] )->save();
}

Laravel 5.6 Unit Test Call to a member function beginTransaction() on null

I have a Laravel project running version 5.6.
The connected database is mongodb with the jenssegers/mongodb package.
I wrote a single Unittest to test a function related to the User.
The test creates new users in a configured test mongodb database.
I want to refresh the database after each test run so I use the RefreshDatabase Trait.
When using the RefreshDatabase Trait I get the following error when running my test:
There was 1 error:
1) Tests\Unit\UserTest::it_gets_top_user
Error: Call to a member function beginTransaction() on null
When not using the Trait the test creates all the necessary stuff in the database and performs the assertion without an error.
The test looks like this:
/** #test */
public function it_gets_top_user()
{
factory(\App\Users\User::class, 5)->create();
$userOne = factory(\App\Users\User::class)->create([
'growth' => 10
]);
$topUser = Users::getTopUser();
$collection = new Collection();
$collection->push($userOne);
$this->assertEquals($collection, $topUser);
}
I use the following versions in my composer.json:
"laravel/framework": "5.6.*",
"jenssegers/mongodb": "3.4.*",
"phpunit/phpunit": "~7.0",
The following versions are used on the server:
PHP 7.2
MongoDB 3.4
PHP MongoDB Extension 1.4.2
I call the test with the phpunit installed in the vendor directory with:
vendor/phpunit/phpunit/phpunit
The problem seems to be, that the RefreshDatabase Trait does not work at all for a MongoDB environment.
I solved the above problem by creating my own RefreshDatabase Trait in the testing/ directory of my Laravel project.
The Trait looks like this:
<?php
namespace Tests;
trait RefreshDatabase
{
/**
* Define hooks to migrate the database before and after each test.
*
* #return void
*/
public function refreshDatabase()
{
$this->dropAllCollections();
}
/**
* Drop all collections of the testing database.
*
* #return void
*/
public function dropAllCollections()
{
$database = $this->app->make('db');
$this->beforeApplicationDestroyed(function () use ($database) {
// list all collections here
$database->dropCollection('users');
});
}
}
To enable this Trait I have overridden the setUpTraits function in the TestCase Class. It now looks like this:
/**
* Boot the testing helper traits.
*
* #return array
*/
protected function setUpTraits()
{
$uses = array_flip(class_uses_recursive(static::class));
if (isset($uses[\Tests\RefreshDatabase::class])) {
$this->refreshDatabase();
}
if (isset($uses[DatabaseMigrations::class])) {
$this->runDatabaseMigrations();
}
if (isset($uses[DatabaseTransactions::class])) {
$this->beginDatabaseTransaction();
}
if (isset($uses[WithoutMiddleware::class])) {
$this->disableMiddlewareForAllTests();
}
if (isset($uses[WithoutEvents::class])) {
$this->disableEventsForAllTests();
}
if (isset($uses[WithFaker::class])) {
$this->setUpFaker();
}
return $uses;
}
And finally in all my testing classes I can use my newly created Trait like this:
<?php
namespace Tests\Unit;
use Illuminate\Database\Eloquent\Collection;
use Tests\RefreshDatabase;
use Tests\TestCase;
class UserTest extends TestCase
{
use RefreshDatabase;
// tests happen here
}

Categories