Redis and symfony PSR16 cache - php

I'm saving a value into Redis cache from a symfony application using Symfony\Component\Cache\Psr16Cache implementation. I do
$cache->set('key',[
'value1' => (new \DateTime())->getTimestamp(),
'value2' => (new \DateTime())->getTimestamp(),
'value3' => 'message'
])
Obviously two \Datetime's are different. What it does is kind of serialization of the array and datetime objects into string like :
127.0.0.10:6379> GET key
"\x00\x00\x00\x02\x14\x03\x11\x16value1\x17\bDateTime\x14\x03\x11\x04date\x11\x1a2019-09-25 09:12:00.000000\x11\rtimezone_type\x06\x03\x11\btimezone\x11\x10America/New_York\x11\x14value2\x1a\x01\x14\x03\x0e\x02\x11\x1a2019-09-28 20:39:00.000000\x0e\x04\x06\x03\x0e\x05\x0e\x06\x11\x13message\x11\x11message"
So it's type of string, not array.
Then I need to read this key from another app. This app uses this Redis class and its hgetall call, which returns (error) WRONGTYPE Operation against a key holding the wrong kind of value for the key I saved above from the other app.
Question: what call from Redis library should I use to get the array from the serialized value that PSR16 symfony implementation saved?

Related

Laravel Request Validation of an object

I would like to filter some data coming from an API payload in which i have to check if some certain part of the data is an object, such as this:
"object"{
"propety":value,
"another_propety":value,
}
I wanna be sure that the "object" that comes from the payload is actually an object, which holds properties and not an integer, neither, an array or any other type...but an object. Is there any way i can solve this by using Laravel's native validator i have to create a custom rule?
Thank you
Considering the laravel lifecycle, By the time the request payload reaches validation the object has already changed to a php array, use the below to validate your key.
$this->validate($request, [
'object' => 'required|array',
'object.property' => 'required|string',
]);
https://laravel.com/docs/5.8/validation#rule-array
also in case it is somehow going to remain a JSON object, check the official documentation for doing so -> https://laravel.com/docs/5.8/validation#rule-json
This will help you identify the object key as JSON while the request gets vaildated.

How can I use codeception seeRecord function for testing contents a column?

I am using Codeception for testing a REST API developed by Laravel.
I use seeRecord method like this for testing value of a columns:
$I->seeRecord('organisations', ['id' => $id, 'first_name' => $value]);
But what the seeRecord does is testing equality. However in some cases I need to test the contents and not the equality, something like:
WHERE columnX like %value%
It seems I can do it with the third parameter of the seeRecord which is [Part] orm while I couldn't find any documentation or sample for that.
Any idea that how can I check the record for like operator?
[Part] orm is not a parameter, it means that it is possible to import only that part of the module (ORM methods) to actor class.
The only way to use different comparison functions with data is to fetch the record and use assertions from Asserts module.
$org = $I->grabRecord('organisations', ['id' => $id]);
$I->assertStringContainsString('value', $org['first_name'], 'First name doesnt contain value');

How can I test that a key does NOT exist at different levels of a multidimensional array using data providers?

I have a method that creates a large multi-dimensional array. I am trying to run a series of unit tests on this method. I'm trying to do both positive tests (testing that certain array keys get set) and negative tests (testing that certain array keys are absent). The problem is that there is a lot of code required to set up the object and there are a lot of different arguments that this method accepts that I want to test. For these reasons, I want to use data providers to run a series of tests on the method. This way I could set up the object once and use the data provider to get the array arguments and expected array values.
I can do the positive tests by calling $this->assertArraySubset() and including the expected array structure in the data provider. But I can't think of a good way to test that certain array keys do not exist (my negative test), because these array keys are at different levels of the array.
Here is an example of my code so you can see what I'm dealing with:
<?php
class MyClassTest {
public function providerForFunctionThatCreatesArray() {
return [
[
'{foo:bar}', # some data returned by service A
'{foo:baz}', # some data returned by service B
'c' # checking that this key does not exist in the array
],
[
'{foo:barbaz}',
'{foo:bazbar}',
'd' # I also want to check that this key does not exist but in a different level of the array (i.e. $array['b'])
],
]
}
/**
* #dataProvider providerForFunctionThatCreatesArray
*/
public function testFunctionThatCreatesArray($dataFromServiceA, $dataFromServiceB, $expectedKeyNotExists) {
$serviceA = $this
->getMockBuilder(ServiceA::class)
->setMethods(['get_data'])
->getMock();
$serviceA->expects($this->any())->method('get_data')->willReturnValue($dataFromServiceA);
$serviceB = $this
->getMockBuilder(ServiceB::class)
->setMethods(['get_data'])
->getMock();
$serviceB->expects($this->any())->method('get_data')->willReturnValue($dataFromServiceB);
$myClass = new MyClass($serviceA, $serviceB);
$array = $myClass->functionThatCreatesArray();
// This is the function that checks that keys do not exist in the array
$this->assertArrayNotHasKey($expectedKeyNotExists, $array['a']);
}
}
The {foo:...} stuff is data that is returned by some services that my function uses. The different values influence the array that my function creates. I have created mocks for these services and use the data provider to force the value that the services return.
As you can see, my data provider also returns a key as the third argument to my test function ($expectedKeyNotExists). This is the key that I'm checking does not exist in my array. However, the d key is one that I want to check at a different part of my array, such as $array['b'] instead of $array['a']. If I run the above test, it will check that 'd' does not exist at $array['a'], which is not what I want. What would be a good way to structure my tests to dynamically check that my keys do not exist in different parts of the array?
I thought about having my data provider return a fourth key which is the parent key to use. Like this:
return [
[
'{foo:bar}', # some data returned by service A
'{foo:baz}', # some data returned by service B
'c', # checking that this key does not exist in the array
'a' # parent key
],
[
'{foo:barbaz}', # some data returned by service A
'{foo:bazbar}', # some data returned by service B
'd', # checking that this key does not exist in the array
'b' # parent key
]
]
And then I could do my tests like this:
public function testFunctionThatCreatesArray($dataFromServiceA, $dataFromServiceB, $expectedKeyNotExists, $parentKey) {
// ... snip ...
$this->assertArrayNotHasKey($expectedKeyNotExists, $array[$parentKey]);
}
The problem with the above method is that it's not very flexible in the case of checking keys at different levels of the array. For example, what if I want to check that keys to not exist at $array['a']['e']['f'] and $array['a']['g']['h'].
As far as I know Phpunit does not offer an assertion for an array key recursively.
You can extend Phpunit with your own assertions, but I would start lightly and add a private helper method to the test-case that returns a bool whether or not the array as the key recursively (check existing Q&A material like Search for a key in an array, recursively and others on how to check an array for a key recursively), and then do an assertion for false like:
$this->assertFalse(
$this->arrayHasKeyRecursive($array, $expected),
"key must not exist"
);
just keep in mind when you write code to support your tests, to make it quite dumb (and sometimes you need to put helper routines under test as well so that your tests do not lie to you on bugs).
Cross reference
Testing iterables in PHPUnit

In Laravel, how can I add items to array in controller and then process after response via App::finish()

I am creating an analytics storage process (using elastic search) where I need to add the items to be stored in elastic from my controller. But then I want to wait until after the response has been sent to the user to actually do the processing. I don't want the user to have to wait for this process to complete before getting the server response.
I am planning on running the processing part using:
App::finish(function(){
// DO THE PROCESSING
})
I know that this will run after the response has been sent. However, I am not sure how to get the data which has been compiled in a controller (or any class) to be referenced in the App::finish() closure method.
I have tried using App::singleton() but there are many instances where I need to be able to continually 'set' the data, I can't just set it once. I guess I am essentially looking for a global variable that I can manipulate but I know that doesn't exist in Laravel.
Another option is to use Session::push() and Session::get() but right now I have my Session storage using memcached for this app and I would rather not do additional I/O on memcached when I could just be storing the data needed to be saved in temporary memory.
Seems like I just need a simple container to write to and read from which is saved only in memory but I cannot find that in the Laravel docs.
You might be able to use the __destruct() magic method on whichever controllers you need to do the processing on.
You could also potentially implement it in BaseController as well if it should run for all controllers.
Another option would be to use sqlite in memory. Simply create a new connection
'sqlite_memory' => array(
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => '',
),
Then you can use DB::connection('sqlite_memory') to use that connection and store/save whatever you need using the query builder.
You can pass data to the closure using "use".
In PHP 5.3.0, what is the function "use" identifier?
I ended up using something like this for storing data in the cache after returning the data to the user.
App::finish(function($request, $response) use ($id, $json){
Cache::put('key_'.$id, $json, 1440);
});

How to use a Yii fixture object that's not in the defaultScope? Is there a way of using resetScope() with Yii fixtures?

Here's a test file:
class MyTest extends CDbTestCase
{
public $fixtures = array(
'my_data' => 'MyData',
);
public function testMyFunction()
{
$myObjectNotInDefaultScope = $this->my_data('out_of_scope_object');
//Can't do anything with $myObjectNotInDefaultScope since it returns null
// Is it possible to use resetScope?
// I can always set a primary key for the object and use findByPk but that's a hack
}
}
and here's the corresponding fixture:
<?php
return array(
'out_of_scope_object' => array(
'title' => 'This one is out of scope',
'status' => 'archived', // so not in the default scope
),
'in_scope_object' => array(
'title' => 'This one is in scope',
'status' => 'active',
),
);
Both rows in the fixture are added to the db table, so that's not the problem. I can access both rows via the primary keys that they're allocated. But I can't access the out of scope object in this way:
$myObjectNotInDefaultScope = $this->my_data('out_of_scope_object');
which when you're testing is really how you want to access it, I think.
I have a less than satisfactory solution in use for now of allocating the object a primary key value and using findByPk (edit: with resetScope()) to load the object. I would prefer to use the normal way of working with fixtures instead, if that's possible.
Edit: To clarify a little in response to some posts:
It is possible to use fixtures as a method to return an object. This would work:
$myObjectInDefaultScope = $this->my_data('in_scope_object');
but this wouldn't work BECAUSE it's not in the default scope and there's seemingly no way currently of running resetScope() for a fixture method call:
$myObjectNotInDefaultScope = $this->my_data('out_of_scope_object');
Why do I need to do this? Well, I might want to test my unarchive method, for example. Seems reasonable to me. (As mentioned before, I can get round this a little inelegantly by using a primary key to load the object corresponding to the fixture).
Although I can access the fixture data as an array using:
$arrayNotInDefaultScope = $this->my_data['out_of_scope_object'];
it's returning an array not an object, so I can't test the object's methods on an array.
To answer my own question, there is currently no way to use resetScope() with Yii fixtures (v 1.14). It could be implemented with some effort but given that Yii2 is on its way, it's probably not worth the effort of generating a pull request that may never make it in to the source.
For now, IMO, the cleanest workaround is:
1) Define a primary key in the fixture
2) Get the primary key from the fixture array and look up the object using it:
$arrayOutOfScopeObject = $this->my_data['out_of_scope_object'];
$myObjectNotInDefaultScope = MyObject::model()
->resetScope()
->findByPk($arrayOutOfScopeObject['id']);
You could, of course, save yourself the effort of looking up the pk from the fixture by hard-coding the pk value in your test code, but that leaves your test code vulnerable to being broken by changes to a fixture that's shared with other tests.
You are using the fixtures as a method, whilst it is an array of object.
So instead of:
$myObjectNotInDefaultScope = $this->my_data('out_of_scope_object');
You should be doing:
$myObjectNotInDefaultScope = $this->my_data['out_of_scope_object'];
Check the guide for more info

Categories