PHPSpec: function returning by reference - php

I updated Doctrine 2.5 to 2.6 in my project and phpspec is broken.
The function getEntityChangeSet() is now returned by reference. It seems not to be supported by phpspec.
$unitOfWork
->getEntityChangeSet($site)
->willReturn(['_dataParent' => [0 => 2, 1 => 3]]);
The response is
returning by reference not supported
the underlying function (doctrine/doctrine2) is
public function & getEntityChangeSet($entity)
{
$oid = spl_object_hash($entity);
$data = [];
if (!isset($this->entityChangeSets[$oid])) {
return $data;
}
return $this->entityChangeSets[$oid];
}
Do you know if it's possible to bypass this or change test for make it work?

The answer has been given on Twitter by #Pamilme
You have to mock UnitOfWork with Mockery. An example can be found here:
/** #var UnitOfWork|MockInterface $unitOfWork */
$unitOfWork = Mockery::mock(UnitOfWork::class);
$unitOfWork->shouldReceive('getEntityChangeSet')->withArgs([$productAttribute->getWrappedObject()])->andReturn([
'configuration' => [
['choices' => [
'8ec40814-adef-4194-af91-5559b5f19236' => 'Banana',
'1739bc61-9e42-4c80-8b9a-f97f0579cccb' => 'Pineapple',
]],
['choices' => [
'8ec40814-adef-4194-af91-5559b5f19236' => 'Banana',
]],
],
]);
$entityManager->getUnitOfWork()->willReturn($unitOfWork);

if you extends TestCase on your test class, you can also do something like:
$uow = $this->createMock(UnitOfWork::class);
$uow->method('getEntityChangeSet')->willReturn(['_dataParent' => [0 => 2, 1 => 3]);

Related

Laravel: Get the correct value of an enum field on a resource class

I'm using PHP8.1 and Laravel 9 for a project in which I've got the following enum:
enum OrderStatuses : string
{
case New = 'new';
case Pending = 'pending';
case Canceled = 'canceled';
case Paid = 'paid';
case PaymentFailed = 'payment-failed';
public function createOrderStatus(Order $order) : OrderStatus
{
return match($this) {
OrderStatuses::Pending => new PendingOrderStatus($order),
OrderStatuses::Canceled => new CanceledOrderStatus($order),
OrderStatuses::Paid => new PaidOrderStatus($order),
OrderStatuses::PaymentFailed => new PaymentFailedOrderStatus($order),
default => new NewOrderStatus($order)
};
}
one of the classes listed in the enum looks like this:
abstract class OrderStatus
{
public function __construct(protected Order $order)
{
}
/**
* Determines whether an order can transition from one status into another
*
* #return bool
*/
abstract public function canBeChanged() : bool;
}
class PaidOrderStatus extends OrderStatus
{
public function canBeChanged(): bool
{
return false;
}
}
all others are basically the same, they just differ on the implementation of the canBeChanged method.
Now, I've got the following resource:
class OrdersResource extends JsonResource
{
public function toArray($request): array
{
return [
'id' => (string)$this->id,
'type' => 'orders',
'attributes' => [
'status' => $this->status,
'payment_type' => $this->payment_type,
'payment_transaction_no' => $this->payment_transaction_no,
'subtotal' => $this->subtotal,
'taxes' => $this->taxes,
'total' => $this->total,
'items' => OrderItemsResource::collection($this->whenLoaded('orderItems')),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
]
];
}
}
which is called from my controller like this:
return (new OrdersResource($order))
->response()->setStatusCode(ResponseAlias::HTTP_OK);
Before implementing the enum my resource was working correctly, it returned the expected data. But after the enum, it's returning [] for the status field.
A sample return is currently looking like this:
"id" => "86b4e2da-76d4-4e66-8016-88a251513050"
"type" => "orders"
"attributes" => array:8 [
"status" => []
"payment_type" => "card"
"payment_transaction_no" => "3kaL92f5UwOG"
"subtotal" => 3005.76
"taxes" => 0
"total" => 3005.76
"created_at" => "2022-08-31T12:47:55.000000Z"
"updated_at" => "2022-08-31T12:47:55.000000Z"
]
]
notice again the value for status.
I've got a casting and a attribute in my Order's model:
protected $casts = [
'status' => OrderStatuses::class,
];
protected function status(): Attribute
{
return new Attribute(
get: fn(string $value) =>
OrderStatuses::from($value)->createOrderStatus($this),
);
}
Furthermore, if I dd the type of $this->status in the toArray method from OrdersResource it says that it is of type Domain\Order\Enums\PaidOrderStatus which is correct.
I tried adding __toString() to PaidOrderStatus class but had no luck. What am I missing?
Update
I've added a test() method to PaidOrderStatus:
class PaidOrderStatus extends OrderStatus
{
public function canBeChanged(): bool
{
return false;
}
public function test() : string
{
return OrderStatuses::Paid->value;
}
}
and then did:
public function toArray($request): array
{
return [
'id' => (string)$this->id,
'type' => 'orders',
'attributes' => [
'status' => ($this->status)->test(),
'payment_type' => $this->payment_type,
'payment_transaction_no' => $this->payment_transaction_no,
'subtotal' => $this->subtotal,
'taxes' => $this->taxes,
'total' => $this->total,
'items' => OrderItemsResource::collection($this->whenLoaded('orderItems')),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
]
];
}
and that gave me:
[
"id" => "8a2d6024-a63f-44ba-a145-cede2ecf3aaa"
"type" => "orders"
"attributes" => array:8 [
"status" => "paid"
"payment_type" => "card"
"payment_transaction_no" => "kC9upaoGb2Nd"
"subtotal" => 657.26
"taxes" => 0
"total" => 657.26
"created_at" => "2022-08-31T13:17:25.000000Z"
"updated_at" => "2022-08-31T13:17:25.000000Z"
]
and that worked. But it's a very hacky solution and I'd like to do better.
I'm sure you've already solved this as it's been months ago, but first you don't need the Attribute mutator as you have already defined the cast which will correctly map the string value to the right enum case.
Then in the resource, you just get the value from the enum like so.
'status' => $this->status->value,

Symfony PHPunit Testing - how do I mock services?

I'm just wondering how you'd mock a service in Symfony.
This is what I've ended up with so far and it's not working as expected. I expect the getVenuesData() method from the NetballFeedService to dump the mocked data in the test (I've set a DD in the command). But when I run the test, it dumps the real time data being pulled from the NetballFeedService.
Test:
public function it_imports_venues(): void
{
$kernel = self::bootKernel();
$netballAPIMock = $this->createMock(NetballFeedService::class);
$netballAPIMock->method('getVenuesData')->willReturn([
800 => ['name' => 'Venue 1', 'location' => 'MEL'],
801 => ['name' => 'Venue 2', 'location' => 'ADE']
]);
$this->getContainer()->set('netballAPI', $netballAPIMock);
$container = $kernel->getContainer();
$container->set('netballAPI', $netballAPIMock);
$application = new Application($kernel);
$command = $application->find('app:feed:import-venues');
$commandTester = new CommandTester($command);
$commandTester->execute([]);
$commandTester->assertCommandIsSuccessful();
}
Command being tested:
protected static $defaultName = 'app:feed:import-venues';
public function __construct(
private readonly NetballFeedService $netballFeedService,
private readonly VenueService $venueService
)
{
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$data = $this->netballFeedService->getVenuesData();
dd($data);
$this->venueService->updateVenuesDataFromFeed($data);
return 0;
}
Class method expected to return mocked data:
public function getVenuesData(): array
{
$response = $this->client->request('GET', self::FIXTURES_URL);
$rawData = json_decode($response->getBody()->getContents(), true);
$rounds = $rawData['data']['results'];
$parsedVenuesData = [];
foreach ($rounds as $round) {
foreach ($round['matches'] as $matches) {
$venueId = $matches['venueId'];
if (array_key_exists($venueId, $matches)) {
continue;
}
$parsedVenuesData[$matches['venueId']] = [
'name' => $matches['venueName'],
'location' => $matches['venueLocation']
];
}
}
ksort($parsedVenuesData);
return $parsedVenuesData;
}
I'm trying to replicate my Laravel test code in Symfony:
/** #test */
public function it_imports_venues()
{
$this->withExceptionHandling();
$this->instance(
NetballAPI::class,
Mockery::mock(NetballAPI::class, function (MockInterface $mock) {
$mock->shouldReceive('getVenuesData')->once()
->andReturn([
800 => [
'name' => 'Venue 1',
'location' => 'MEL'
],
801 => [
'name' => 'Venue 2',
'location' => 'ADE'
],
]);
})
);
$this->artisan('import:venues');
$this->assertDatabaseHas('venues', [
'id' => 800,
'name' => 'Venue 1',
'location' => 'MEL'
]);
$this->assertDatabaseHas('venues', [
'id' => 801,
'name' => 'Venue 2',
'location' => 'ADE'
]);
services_test.yml:
services:
_defaults:
public: true
App\Service\NetballFeedService:
public: true
alias: netballAPI
Just wondering how I can mock the NetballFeedService class in the test instance? Thanks in advance.
I was configuring the service_test.yaml wrong. It should be:
services:
App\Service\NetballFeedService:
public: true
app.netballFeedService:
alias: App\Service\NetballFeedService
public: true
Also, I'm not really used to using YAML files for configs. It's not very readable especially compared to it's PHP file counterpart in this instance. So I'll be experimenting on using both YAML & PHP files for configs/routes etc.

How may I test in CakePHP with Pest

My problem:
How my I test some MVC part with Pest in CakePHP, for example:
When I´m use PHPUnit, I´ll type:
public class ItemTest extends TestCase
And it will show what thing that I wanna test, but how do that with Pest?
(Obs.: I´m really new on tests, so I accept anything that will make me improve :) )
After you installed Pest you just have to follow its syntax and documentation, removing all the OOP code and just using test() or it(). The file name will be the same. This is an example from CakePHP docs:
class ArticlesTableTest extends TestCase
{
public $fixtures = ['app.Articles'];
public function setUp()
{
parent::setUp();
$this->Articles = TableRegistry::getTableLocator()->get('Articles');
}
public function testFindPublished()
{
$query = $this->Articles->find('published');
$this->assertInstanceOf('Cake\ORM\Query', $query);
$result = $query->enableHydration(false)->toArray();
$expected = [
['id' => 1, 'title' => 'First Article'],
['id' => 2, 'title' => 'Second Article'],
['id' => 3, 'title' => 'Third Article']
];
$this->assertEquals($expected, $result);
}
}
With Pest, it will become:
beforeEach(function () {
$this->Articles = TableRegistry::getTableLocator()->get('Articles');
});
test('findPublished', function () {
$query = $this->Articles->find('published');
expect($query)->toBeInstanceOf('Cake\ORM\Query');
$result = $query->enableHydration(false)->toArray();
$expected = [
['id' => 1, 'title' => 'First Article'],
['id' => 2, 'title' => 'Second Article'],
['id' => 3, 'title' => 'Third Article']
];
expect($result)->toBe($expected);
});
Note that toBeInstanceOf() and toBe() are part of the available Pest expectations.
Finally, instead of running your test suite with $ ./vendor/bin/phpunit, you will run it with $ ./vendor/bin/pest.

laravel elasticsearch babenkoivan/elastic-adapter model relation

I'm looking to use elastic search on a project with model relation.
For now elastic search is working, I've followed this doc who explain how to start with this package :
elasticsearch/elasticsearch
babenkoivan/elastic-migrations
babenkoivan/elastic-adapter
babenkoivan/elastic-scout-driver
The problem is I need to able to search by relation.
this is my composant elastic migration :
Index::create('composant', function(Mapping $mapping, Settings $settings){
$mapping->text('reference');
$mapping->keyword('designation');
$mapping->join('categorie');
$settings->analysis([
'analyzer' => [
'reference' => [
'type' => 'custom',
'tokenizer' => 'whitespace'
],
'designation' => [
'type' => 'custom',
'tokenizer' => 'whitespace'
]
]
]);
});
Here my categorie elastic migration :
Index::create('categorie', function(Mapping $mapping, Settings $settings){
$mapping->keyword('nom');
$settings->analysis([
'analyzer' => [
'nom' => [
'type' => 'custom',
'tokenizer' => 'whitespace'
]
]
]);
});
My composant Model :
public function categorie()
{
return $this->belongsTo('App\Model\Categorie');
}
public function toSearchableArray()
{
return [
'reference' => $this->reference,
'designation' => $this->designation,
'categorie' => $this->categorie(),
];
}
and my categorie Model :
public function toSearchableArray()
{
return [
'nom' => $this->nom,
];
}
So if you look at the composant relation, you can see that the join mapping return the categorie relation. I dont now if I do it right but what I know is that elasticsearch didn't have any relation in the object I'm looking for.
And I didn't find any doc of how to use the join mapping method of the package.
OK, I've found the solution, the problem was in the migration you must use object in order to index the belongsToMany relationship like that
Index::create('stages', function (Mapping $mapping, Settings $settings) {
$mapping->text('intitule_stage');
$mapping->text('objectifs');
$mapping->text('contenu');
$mapping->object('mots_cles');
});
and in your model :
public function toSearchableArray()
{
return [
'intitule_stage' => $this->intitule_stage,
'objectifs' => $this->objectifs,
'contenu' => $this->contenu,
'n_stage' => $this->n_stage,
'mots_cles' => $this->motsCles()->get(),
];
}
And the result is as expected now
If you want to get "nom" of categorie, write this in composant Model instead
'categorie' => $this->categorie->nom ?? null,
$this->categorie() return the relationship, not the object.
Same problem with a belontoMany relation, and I've made the same things in order to get the relation as a nested object, but when I try to populate my index the field "mots_cles" stay empty, I don't understand why.
Here is the migration :
Index::create('stages', function (Mapping $mapping, Settings $settings) {
$mapping->text('intitule_stage');
$mapping->text('objectifs');
$mapping->text('contenu');
$mapping->nested('motsCles', [
'properties' => [
'mot_cle' => [
'type' => 'keyword',
],
],
]);
});
The model :
public function toSearchableArray()
{
return [
'intitule_stage' => $this->intitule_stage,
'objectifs' => $this->objectifs,
'contenu' => $this->contenu,
'n_stage' => $this->n_stage,
'mots_cles' => $this->motsCles(),
];
}
public function motsCles()
{
return $this->belongsToMany(MotsCle::class);
}

TYPO3 v10 How do i get an object's crdate or the tstamp in FrontEnd

I am using TYPO3 v10 and i have built an extension.
In some parts of the FrontEnd i need to get the crdate or the tstamp. TYPO3 does not have getters and setters to use in order to retrieve them.
Before TYPO3 v10 you could do something like that:
config.tx_extbase {
persistence {
classes {
TYPO3\CMS\Extbase\Domain\Model\FileMount {
mapping {
tableName = sys_filemounts
columns {
title.mapOnProperty = title
path.mapOnProperty = path
base.mapOnProperty = isAbsolutePath
}
}
}
}
}
}
Best regards,
The solution is rather simple. I had to build my own getters and map them. Example the crdate:
Model1.php
/**
* #var \DateTime
*/
protected $creationDate;
public function getCreationDate(): \DateTime
{
return $this->creationDate;
}
tx_extension_domain_model_model1.php
'crdate' => [
'exclude' => true,
'config' => [
'type' => 'select',
'renderType' => 'inputDateTime',
'eval' => 'datetime,int',
'default' => 0,
'range' => [
'upper' => mktime(0, 0, 0, 1, 1, 2038),
],
'behaviour' => [
'allowLanguageSynchronization' => true,
],
],
],
your_extension/Configuration/Extbase/Persistence/Classes.php
return [
\Vendor\ExtensionName\Domain\Model\Model1::class => [
'tableName' => 'tx_extension_domain_model_model1',
'properties' => [
'creationDate' => [
'fieldName' => 'crdate',
],
// ...
],
],
];
Now the crdate can be retrieved in the FrontEnd
Big Thanks to #Mathias Brodala for pointing me to the right direction.
Best regards

Categories