PHP static objects giving a fatal error - oop

I have the following PHP code;
<?php
component_customer_init();
component_customer_go();
function component_customer_init()
{
$customer = Customer::getInstance();
$customer->set(1);
}
function component_customer_go()
{
$customer = Customer::getInstance();
$customer->get();
}
class Customer
{
public $id;
static $class = false;
static function getInstance()
{
if(self::$class == false)
{
self::$class = new Customer;
}
else
{
return self::$class;
}
}
public function set($id)
{
$this->id = $id;
}
public function get()
{
print $this->id;
}
}
?>
I get the following error;
Fatal error: Call to a member function set() on a non-object in /.../classes/customer.php on line 9
Can anyone tell me why I get this error? I know this code might look strange, but it's based on a component system that I'm writing for a CMS. The aim is to be able to replace HTML tags in the template e.g.;
<!-- component:customer-login -->
with;
<?php component_customer_login(); ?>
I also need to call pre-render methods of the "Customer" class to validate forms before output is made etc.
If anyone can think of a better way, please let me know but in the first instance, I'd like to know why I get the "Fatal error" mentioned above.

Well, I think your Customer::getInstance() method is flawed. It should look like this:
...
static function getInstance()
{
if(self::$class == false)
{
self::$class = new Customer;
return self::$class; // ADDED!!
}
else
{
return self::$class;
}
}
....
In the if(self::$class == false) branch you are creating the instance of the class, but you dont return it.
You could also rewrite it as such:
static function getInstance()
{
if(self::$class == false)
{
self::$class = new Customer;
}
return self::$class;
}
To make it a bit shorter.

DRY: Don't Repeat Yourself
static function getInstance()
{
if(self::$class == false)
{
self::$class = new Customer;
}
return self::$class;
}
And for Sinlgetons it is also important to prevent __clone() from being used. Making it private should solve that problem:
private function __clone() {}

Related

Exclude specific products from Product Indexer in Shopware 6

We have four specific products with a massive amount of variants. When running the Product Indexer we run out of memory because of these products.
So we want to exclude these specific products from the Product Indexer Job.
My first approach was to use the ProductIndexerEvent, but the event is dispatched at the end of the handle() method :
(vendor/shopware/core/Content/Product/DataAbstractionLayer/ProductIndexer.php:187),
which is probably too late.
What is the best approach to implement that behaviour?
I would advise against excluding products from being indexed. There's business logic relying on the data being indexed.
If you're confident in what you're doing and know about the consequences, you could decorate the ProductIndexer service.
<service id="Foo\MyPlugin\ProductIndexerDecorator" decorates="Shopware\Core\Content\Product\DataAbstractionLayer\ProductIndexer">
<argument type="service" id="Foo\MyPlugin\ProductIndexerDecorator.inner"/>
</service>
In the decorator you would have to deconstruct the original event, filter the WriteResult instances by excluded IDs and then pass the reconstructed event to the decorated service.
class ProductIndexerDecorator extends EntityIndexer
{
const FILTERED_IDS = ['9b180c61ddef4dad89e9f3b9fa13f3be'];
private EntityIndexer $decorated;
public function __construct(EntityIndexer $decorated)
{
$this->decorated = $decorated;
}
public function getDecorated(): EntityIndexer
{
return $this->decorated;
}
public function getName(): string
{
return $this->getDecorated()->getName();
}
public function iterate($offset): ?EntityIndexingMessage
{
return $this->getDecorated()->iterate($offset);
}
public function update(EntityWrittenContainerEvent $event): ?EntityIndexingMessage
{
$originalEvents = clone $event->getEvents();
if (!$originalEvents) {
return $this->getDecorated()->update($event);
}
$event->getEvents()->clear();
/** #var EntityWrittenEvent $writtenEvent */
foreach ($originalEvents as $writtenEvent) {
if ($writtenEvent->getEntityName() !== 'product') {
$event->getEvents()->add($writtenEvent);
continue;
}
$results = [];
foreach ($writtenEvent->getWriteResults() as $result) {
if (\in_array($result->getPrimaryKey(), self::FILTERED_IDS, true)) {
continue;
}
$results[] = $result;
}
$event->getEvents()->add(new EntityWrittenEvent('product', $results, $event->getContext()));
}
return $this->getDecorated()->update($event);
}
public function handle(EntityIndexingMessage $message): void
{
$data = array_diff($message->getData(), self::FILTERED_IDS);
$newMessage = new ProductIndexingMessage($data, $message->getOffset(), $message->getContext(), $message->forceQueue());
$this->getDecorated()->handle($newMessage);
}
public function getTotal(): int
{
return $this->getDecorated()->getTotal();
}
public function getOptions(): array
{
return $this->getDecorated()->getOptions();
}
}

Doctrine ORM create method phpspec test failure

I try to write, at first glance, it would seem a trivial test for my repository's "update" method:
<?php
declare(strict_types=1);
namespace Paneric\Authorization\ORM\Repository;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\ORMException;
use Paneric\Authorization\DTO\FieldDTO;
use Paneric\Authorization\ORM\Entity\Field;
use Doctrine\ORM\EntityRepository;
use Paneric\Authorization\Interfaces\FieldRepositoryInterface;
class FieldRepository extends EntityRepository implements FieldRepositoryInterface
{
const ENTITY_CLASS = Field::class;
public function __construct(EntityManagerInterface $_em)
{
parent::__construct($_em, $_em->getClassMetadata(self::ENTITY_CLASS));
}
...
public function update(int $fieldId, FieldDTO $fieldDTO): void
{
try {
$field = $this->find($fieldId);
$field->transfer($fieldDTO);
$this->_em->flush();
} catch (ORMException $e) {
echo $e->getMessage();
}
}
...
}
with a spec method:
<?php
namespace spec\Paneric\Authorization\ORM\Repository;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\QueryBuilder;
use Paneric\Authorization\DTO\FieldDTO;
use Paneric\Authorization\ORM\Entity\Field;
use Paneric\Authorization\ORM\Repository\FieldRepository;
use Doctrine\ORM\AbstractQuery;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class FieldRepositorySpec extends ObjectBehavior
{
public function let(EntityManagerInterface $_em, ClassMetadata $classMetadata)
{
$_em->getClassMetadata(Field::class)->willReturn($classMetadata);
$this->beConstructedWith($_em);
}
...
public function it_updates(Field $field, FieldDTO $fieldDTO, EntityManagerInterface $_em)
{
$fieldId = 1;
$field = $this->find($fieldId);
$field->transfer($fieldDTO)->shouldBeCalled();
$_em->flush()->shouldBeCalled();
$this->update($fieldId, $fieldDTO);
}
...
}
and receive the following error:
Unexpected method call on Double\EntityManagerInterface\EntityManagerInterface\P1:
- find(
null,
1,
null,
null
)
expected calls were:
- getClassMetadata(
exact("Paneric\Authorization\ORM\Entity\Field")
)
- find(
exact("Paneric\Authorization\ORM\Entity\Field"),
exact(1)
)
- flush(
)
Apparently issue is related to the call:
...
$field = $this->find($fieldId);
...
Although the second remark related to getClassMetadata, looks strange, considering the fact that my spec let method:
public function let(EntityManagerInterface $_em, ClassMetadata $classMetadata)
{
$_em->getClassMetadata(Field::class)->willReturn($classMetadata);
$this->beConstructedWith($_em);
}
does its job in case of other spec tests.
Can anyone help me to solve this issue ? Thx in advance.
In my repository's "update" metod, line:
$field = $this->find($fieldId);
has to be replaced by:
$field = $this->_em->find(Field::class, $fieldId);
so the complete spec test looks like:
public function it_updates(Field $field, FieldDTO $fieldDTO, EntityManagerInterface $_em)
{
$fieldId = 1;
$_em->find(Field::class, $fieldId)->willReturn($field);
$field->transfer($fieldDTO)->shouldBeCalled();
$_em->flush()->shouldBeCalled();
$this->update($fieldId, $fieldDTO);
}

Is Sylius PayumBundle handling payment details incorrectly?

I am testing Bitbag/PayUPlugin and I was stopped by gateway API with error "Required data missing".
After some debugging, I realised that Sylius Payment entity, specifically "details" property, is not fulfilled with data.
After change condition on line 53:
https://github.com/Sylius/Sylius/blob/4e06a4dfb8dc56731470016bb97165f3025947b7/src/Sylius/Bundle/PayumBundle/Action/CapturePaymentAction.php#L53
to
if ($status->isNew() || $status->isUnknown()) {
payment gateway seems to work correctly.
Is it a bug or am I doing something wrong ?
Sylius/Sylius v1.4.6
Bitbag/PayUPlugin v1.8.0
Unlikely there is an error in PayumBundle/CapturePaymentAction (because more people used PayumBundle than PayUPlugin, so probability of bug is less), conceptually payment object status at the beginning should be "new" instead of "unknown", so the condition should work.
So you should find out https://github.com/BitBagCommerce/SyliusPayUPlugin/blob/master/src/Action/StatusAction.php#L58 class, why it doesn't reach markNew() line.
I guess the BitBagCommerce/SyliusPayUPlugin is dead as this issue hasn't been addressed yet, since July.
In order to fix this I had to decorate the StatusAction class:
<?php
declare(strict_types=1);
namespace App\Payment\PayU;
use BitBag\SyliusPayUPlugin\Action\StatusAction;
use BitBag\SyliusPayUPlugin\Bridge\OpenPayUBridgeInterface;
use Payum\Core\Action\ActionInterface;
use Payum\Core\Bridge\Spl\ArrayObject;
use Payum\Core\Exception\RequestNotSupportedException;
use Payum\Core\Request\GetStatusInterface;
final class StatusActionDecorator implements ActionInterface
{
private $action;
public function __construct(StatusAction $action)
{
$this->action = $action;
}
public function setApi($api): void
{
$this->action->setApi($api);
}
public function execute($request): void
{
/** #var $request GetStatusInterface */
RequestNotSupportedException::assertSupports($this, $request);
$model = ArrayObject::ensureArrayObject($request->getModel());
$status = $model['statusPayU'] ?? null;
if (empty($status) || OpenPayUBridgeInterface::NEW_API_STATUS === $status) {
$request->markNew();
return;
}
if (OpenPayUBridgeInterface::PENDING_API_STATUS === $status) {
$request->markPending();
return;
}
if (OpenPayUBridgeInterface::CANCELED_API_STATUS === $status) {
$request->markCanceled();
return;
}
if (OpenPayUBridgeInterface::WAITING_FOR_CONFIRMATION_PAYMENT_STATUS === $status) {
$request->markSuspended();
return;
}
if (OpenPayUBridgeInterface::COMPLETED_API_STATUS === $status) {
$request->markCaptured();
return;
}
$request->markUnknown();
}
public function supports($request): bool
{
return $this->action->supports($request);
}
}
then in the services.yaml:
App\Payment\PayU\StatusActionDecorator:
decorates: bitbag.payu_plugin.action.status
arguments: ['#App\Payment\PayU\StatusActionDecorator.inner']

PHPUnit testing a protected static method that uses pdo

I am very new to TDD. I am using phpunit 7.4x-dev. I have the following abstract class that I am trying to develop unit tests for.
use PDO;
abstract class Model {
protected static function getDB() {
static $db = null;
if ($db === null) {
$db = new PDO(ConfigDatabase::DSN, ConfigDatabase::USER, ConfigDatabase::PASSWORD);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
return $db;
}
}
I have created the following test to get around the need to deal with the static protected method. And it works if I provide "ConfigureDatabase" class.
use PHPUnit\Framework\TestCase;
class ModelTest extends TestCase {
function newMockClass(){
$stub = new class() extends Model{
function getStaticMethod($methodName){
return self::$methodName();
}
};
return $stub;
}
public function testDatabaseExists() {
$stub = $this->newMockClass();
$db = $stub->getStaticMethod('getDB');
$this->assertInstanceOf(PDO::class,$db);
}
}
Since I do not want my tests to rely on any actual database, How would I fake the calls to PDO.
Following Dormilich suggestion I developed a database interface, just in case I decide later I do not want to use PDO.
interface CRUDImp {
function __construct($datbaseBridgeLikePDO);
...
}
Next I wrote my tests for the constructor. I used setup to make sure I was starting with a fresh mock of \PDO.
class PDOWrapperTest extends TestCase {
private $pdoMock;
private $db;
function setup() {
$this->pdoMock = $this->createMock('\PDO');
$this->db = new PDOWrapper($this->pdoMock);
}
public function testWrapperExists() {
$this->pdoMock->method('getAttribute')->willReturn(\PDO::ERRMODE_EXCEPTION);
$db = new PDOWrapper($this->pdoMock);
$x = $db instanceof CRUDImp;
$this->assertTrue($x);
}
/**
* #expectedException \Exception
*/
public function testNonPDOPassedToConstructor() {
$mock = $this->createMock('\Exception');
$x = new PDOWrapper($mock);
}
...
}
Since PHP is loosely typed I check to make sure that the class passed to the constructor was an instance of \PDO. I implemented the concrete class as follows
class PDOWrapper implements CRUDImp {
private $pdo;
private $dataOutputType = \PDO::FETCH_ASSOC;
public function __construct($pdo) {
if (!($pdo instanceof \PDO)) {
throw new \Exception("PDOWrapper must be passed instance of \PDO");
}
$attr_Errmode = $pdo->getAttribute(\PDO::ATTR_ERRMODE);
if ($attr_Errmode !== \PDO::ERRMODE_EXCEPTION) {
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
}
$this->pdo = $pdo;
}
...
}
Now that I have an independent database wrapper the original Model tests are at the moment trivial and no longer needed. The abstract class Model was modified as follows:
abstract class Model {
protected $database=null;
function __construct(CRUDWrapper $database) {
$this->database = $database;
}
...
}
So for those not familiar with dependency injection I found the following links helpful:
http://php-di.org/doc/understanding-di.html
https://codeinphp.github.io/post/dependency-injection-in-php/
https://designpatternsphp.readthedocs.io/en/latest/Structural/DependencyInjection/README.html
Hope this shortens someone's work.

Laravel Testing Error

I just started with learning how to test within Laravel. I came across some problems though..
I'm testing my controller and want to check if a View has a variable assigned.
My controller code:
class PagesController extends \BaseController {
protected $post;
public function __construct(Post $post) {
$this->post = $post;
}
public function index() {
$posts = $this->post->all();
return View::make('hello', ['posts' => $posts]);
}
}
And my view contains a foreach loop to display all posts:
#foreach ($posts as $post)
{{post->id}}
#endforeach
Last but not least my test file:
class PostControllerTest extends TestCase {
public function __construct()
{
// We have no interest in testing Eloquent
$this->mock = Mockery::mock('Eloquent', 'Post');
}
public function tearDown()
{
Mockery::close();
}
public function testIndex() {
$this->mock->shouldReceive('all')->once()->andReturn('foo');
$this->app->instance('Post', $this->mock);
$this->call('GET', '/');
$this->assertViewHas('posts');
}
}
Now comes the problem, when I run "phpunit" the following error appears:
ErrorException: Invalid argument supplied for foreach()
Any ideas why phpunit returns this error?
Your problem is here:
$this->mock->shouldReceive('all')->once()->andReturn('foo');
$this->post->all() (which is what you're mocking) should return an array, and that's what your view expects. You're returning a string.
$this->mock->shouldReceive('all')->once()->andReturn(array('foo'));
should take care of the error you have, though you'll then get an error of the "Getting property of non-object" type.
You could do this:
$mockPost = new stdClass();
$mockPost->id = 1;
$this->mock->shouldReceive('all')->once()->andReturn(array($mockpost));
You should mock the view as well:
public function testIndex() {
$this->mock->shouldReceive('all')->once()->andReturn('foo');
$this->app->instance('Post', $this->mock);
View::shouldReceive('make')->with('hello', array('posts', 'foo'))->once();
$this->call('GET', '/');
}