How can I test time dependend services? (Mocking DateTime) - php

given that I have a class like this one:
class DefaultProductContainer
{
public function __invoke(array $data, array $mandant): Product
{
$date = new \DateTime('now');
$metaData = [
"createdAt" => '',
"updatedAt" => $date->format('Y-m-d H:i:s.u'),
"status" => ['de'],
];
...
return Product::fromArray($params);
}
....
I want to test, if the created object is equal to what I expect.
But of course they differ, because the time they have been created differs.
Now I know from javascript how to test it with Faketimers. See How do I set a mock date in Jest?
Which is quite neat, because it enables to make such code testable, without changing the original code. And has many more advantages, when we are talking about date-calculations.
I searched now for more than two hours. Is there anything similar for PHP?

Related

Dynamic contructor values for unit test in php

I am currently in the process of doing some refactoring. The stuff is not done by me. I just have to deal with it.
$expected = new instance(0,0,Argument::any());
$result = $this->otherInstance->returnsInstance([]);
$this->assertEquals($expected, $result);
Instance is some kind of model, which is returned by otherInstance. The problem is that the third argument is dynamic and an integer. It can be anything. As you can see, it is mandatory for instantiation of the model. Can this be mocked somehow? How do I set up the test properly?
This does obviously not work ...
::__construct() must be of the type integer, object given
So, how do I mock this? Or how do I set up the test in such a way as to handle dynamic values? The language level is 7.1, but I want to move to 7.4 soon.
One way to work with different test scenarios is using Data Providers.
In the example below, you set some arbitrary values in the method provideModelConstructorArguments. The test testMyTest will run twice, one time for each set of values present in the data provider.
public function provideModelConstructorArguments()
{
return [
[
'argument1' => 0,
'argument2' => 1,
'argument3' => 100
],
[
'argument1' => 5,
'argument2' => 3,
'argument3' => 57
]
];
}
/**
* #dataProvider provideModelConstructorArguments
**/
public function testMyTest($argument1, $argument2, $argument3)
{
$expected = new instance($argument1, $argument2, $argument3);
//continue your test code
}

Elegently handle property of non-object errors that surface from different locations in code

My app reads from a DB that get's written by another API, now in some outlandish cases (that actually happened today) it wrote a customer id of 0, which ofcourse, does not exist.
I am looking for an elegant 'from-the-top' model or even presenter solution for handling erroneous ID's that do not exist.
So instead of finding every $whatever->customer->id in my app and then writing in an isset()/empty() ternary function, I am looking to pacify this error in a more elegant way where any customer instantiation/eloquent object would send the string "NA" to a non existent object, so even if an email/phone/etc or any other column of customer model, it would return a simple "NA" string.
I am struggling to find an eloquent solution that would provide 1 point of change.
you can use withDefault() modifier on your relationship.
example:
use Illuminate\Database\Eloquent\Model;
class Whatever extends Model {
public function customer() {
return $this->belongsTo(Customer::class, 'customer_id', 'id')
->withDefault([
'id' => 'NA',
'name' => 'Unknown'
// etc
]);
}
}
I would suggest you take a look at a Laravel class that most people don't know about. That is Fluent.
It allows you to do stuff like this:
$fluent = new Fluent([
'one' => 1,
'two => 2,
]);
echo $fluent->get('one'); // returns 1
echo $fluent->get('three'); // returns null
echo $fluent->get('three', 3); // returns 3
As you can imagine, it's perfect to use with third-party APIs and data that sometimes provide unexpected results. You can also do a lot more with Fluent.
Alternatively, you could look into Laravel helpers such as array_get(). From the documentation:
The array_get function retrieves a value from a deeply nested array using "dot" notation:
$array = ['products' => ['desk' => ['price' => 100]]];
$price = array_get($array, 'products.desk.price');
// 100
The array_get function also accepts a default value, which will be returned if the specific key is not found:
$discount = array_get($array, 'products.desk.discount', 0);
// 0

ZF2 validating date and time format PT_BR always The input does not appear to be a valid date

Project utilizing ZF2 + Doctrine 2.
I Tried many formats. I'm working with a Form without validation.
My last try was:
$traindate = new Element\DateTime('trainDate');
$traindate->setAttributes(array(
'name' => 'trainDate',
'id' => 'trainDate',
'size' => '30',
'class' => 'datepicker',
'options' => array(
'label' => '',
'format' => 'd-m-Y H:i'
),
));
I need to use a input to set a date and time of a event. On Brazil the basic format is:
14-05-2014 14:20
15-05-2015 15:00
With means Days Months Year Hour Minutes, like I'm expressing on the Options -> Format.
This way always when I try to insert, i get the following messsage:
The input does not appear to be a valid date
Removing the format, i can only pass by $form->isValid($data) by Y-m-d (American Format), but by the way i can't pass time to date too, which is causing me big troubles.
I need to set date PT_BR on Form/Input, Pass by validation, Convert Data do Mysql Format (YYYY-mm-dd HH:ii:ss).
And then retrive from db and convert back to pt_br format.
But not even i can pass time with date to zf2 form, ever this error message.
I remove all filters from this form trying to get work, but doesn't work.
Where is the main problem?
After a long time paying my attention to this problem I found the right and quick solution.
After 6 month making science, I got:
Right all:
$traindate = new Element\DateTime('trainDate');
$traindate->setAttributes(array(
'name' => 'trainDate',
'id' => 'trainDate',
'size' => '30',
'class' => 'datepicker',
));
$traindate->setFormat('d/m/Y'); //ONLY WORKS ON THIS FORMAT.
Docs and people over internet don't make it clear, but to set Format only works on this form.
And to grab this to Entity, you need to write your own Hydrator extending the DoctrineHydrator:
namespace Application\Hydrator;
use DoctrineModule\Stdlib\Hydrator\DoctrineObject;
class MyCustomHydrator extends DoctrineObject {
protected function handleTypeConversions($value, $typeOfField)
{
if($typeOfField == 'datetime'){
return \DateTime::createFromFormat('d/m/Y', $value);
}
return parent::handleTypeConversions($value, $typeOfField);
}
}
It's make it simple to work with any date format. You can extend further making Locale assertions on this Custom Hydrator as you want.
I recommend you to do the date conversion work in your Train entity. The trainData's property getter and setter could be:
//THE PROPERTY IS IN MYSQL FORMAT, BUT THE GETTER WILL RETURN IT ALWAYS IN THE BRAZILIAN ONE
public function getTrainDateTime() {
return $this->trainDateTime->format( 'd/m/Y H:i' );
}
//IT WILL ALWAYS RECIEVE A BRAZILIAN DATETIME, CONVERT IT TO MYSQL FORMAT
public function setTrainDateTime( $trainDateTime ) {
$time = \DateTime::createFromFormat( 'd/m/Y H:i', $trainDateTime )->getTimestamp();
$this->trainDateTime = new \DateTime( date( 'Y-m-d', $time ) );
return $this;
}
If you do it this way, you can always work freely with brazilian dates, without worring about formats. The dirty work will be done by the entity class.

Lithium: Run filter after find() to format output

I wanted to specify the output of a field from within my model so I added a date key to my $_schema:
models/Tags.php
<?php
protected $_schema = array(
'id' => array('type' => 'integer', 'key' => 'primary'),
'title' => array('type' => 'string'),
'created' => array('type' => 'integer', 'date' => 'F jS, Y - g:i a'),
'modified' => array('type' => 'integer')
);
?>
I store my time as an unsigned integer in the db (output of time()).
I want my base model to format any field that has the date key for output. I thought the best place to do that would be right after a find:
extensions/data/Model.php
<?php
static::applyFilter('find', function($self, $params, $chain) {
$schema = $self::schema();
$entity = $chain->next($self, $params, $chain);
foreach ($schema as $field => $options) {
if (array_key_exists('date', $options)) {
//format as a date
$params['data'][$field] = $entity->formatDate($field, $options['date']);
}
}
return $entity;
});
public function formatdate($entity, $field, $format, $timezone = 'Asia/Colombo') {
$dt = new \DateTime();
$tz = new \DateTimeZone($timezone);
$dt->setTimestamp($entity->$field);
$dt->setTimezone($tz);
return $dt->format($format);
}
?>
This doesn't seem to be working. When I execute a find all, this filter seems to get hit twice. The first time, $entity contains a count() of the results and only on the second hit does it contain the Records object.
What am I doing wrong? How do I alter this so that simply doing <?= $tag->created; ?> in my view will format the date the way I want? This, essentially, needs to be an 'after filter', of sorts.
EDIT
If I can find a way to access the current model entity object (not the full namespaced path, $self contains that), I can probably solve my problem.
Regardless of a small fix for your after find filter, I would do it differently.
Every time you'll do a find, you'll override your date format, even if you don't want to display it, but only do a business logic like comparing dates etc ...
Since you want to format your output only in your views (we are not talking about formatting on the fly json responses for an API, etc.), why not using a helper method ?
An other way is to add an instance method in your model (or a BaseModel), called created_at(). Then, you will call it from a view with <?= $entity->created_at() ?>
You can still force a format fetched from your $_schema, or pass it as a param, etc ...
A helper seems cleaner as we are talking about presenting data in your views.
I'm reading the OP's problem as the find filter executes twice. If that's right, then why not just check to see if the $entity contains a recordset?

does sfWidgetFormSelect provide a string or an int of the selected item?

I'm having an annoying problem. I'm trying to find out what fields of a form were changed, and then insert that into a table. I managed to var_dump in doUpdateObjectas shown in the following
public function doUpdateObject($values)
{
parent::doUpdateObject($values);
var_dump($this->getObject()->getModified(false));
var_dump($this->getObject()->getModified(true));
}
And it seems like $this->getObject()->getModified seems to work in giving me both before and after values by setting it to either true or false.
The problem that I'm facing right now is that, some how, sfWidgetFormSelect seems to be saving one of my fields as a string. before saving, that exact same field was an int. (I got this idea by var_dump both before and after).
Here is what the results on both var dumps showed:
array(1) {["annoying_field"]=> int(3)} array(1) {["annoying_field"]=>string(1)"3"}
This seems to cause doctrine to think that this is a modification and thus gives a false positive.
In my base form, I have
under $this->getWidgets()
'annoying_field' => new sfWidgetFormInputText(),
under $this->setValidators
'annoying_field' => new sfValidatorInteger(array('required' => false)),
and lastly in my configured Form.class.php I have reconfigured the file as such:
$this->widgetSchema['annoying_field'] = new sfWidgetFormSelect(array('choices' => $statuses));
statuses is an array containing values like {""a", "b", "c", "d"}
and I just want the index of the status to be stored in the database.
And also how can I insert the changes into another database table? let's say my Log table?
Any ideas and advice as to why this is happen is appreciated, I've been trying to figure it out and browsing google for various keywords with no avail.
Thanks!
Edit:
ok so I created another field, integer in my schema just for testing.
I created an entry, saved it, and edited it.
this time the same thing happened!
first if you what the status_id to be saved in the database, you should define your status array like this:
{1 => "a", 2 => "b", 3 => "c", 4 => "d"}
So that way he know that 1 should be rendered like "a" and so on. Then, when saving, only the index should be saved.
About saving in another database, my advise is to modify the doSave method defined by the Form class yo match your needs. I only know how Propel deals with it, maybe this could help:
the doSave method dose something like this:
protected function doSave($con = null)
{
if (null === $con)
{
$con = $this->getConnection();
}
$old = $this->getObject()->getModifiedValues($this);//Define this
$new_object = new Log($old);//Create a new log entry
$new_object->save($con));//save it!
$this->updateObject();
$this->getObject()->save($con);
// embedded forms
$this->saveEmbeddedForms($con);
}
Hope this helps!
Edit:
This is an example extracted from a model in one of my applications and its working ok:
Schema:
[...]
funding_source_id:
type: integer
required: true
[...]
Form:
$this->setWidget('funding_source_id', new sfWidgetFormChoice(array(
'choices' => array(1 => 'asdads', 2 => '123123123' , 3 => 'asd23qsdf'),
)));
$this->setValidator('funding_source_id', new sfValidatorChoice(array(
'choices' => array(1 => 'asdads', 2 => '123123123' , 3 => 'asd23qsdf'),
'required' => true
)));
About the log thing, that could be quite more complex, you should read the current implementation of the doSave method in the base form class, currently sfFomrObject on Symfony1.4., and when and how it delegates object dealing with modified values.
Okay,
It turns out I forgot to do a custom validator to use the array key instead.

Categories