How to pass arguments to a mocked class constructor - laravel

I have one method (getUsers()) in my class that I would like to mock but I have a constructor in my class. How can I pass values to the constructor when mocking my class?
class MyNotifications {
/**
* Date time.
*
* #var DateTime|mixed|null
*/
public $date;
public function __construct($date = NULL)
{
if (!$date) {
$date = new \DateTime();
}
$this->date = $date;
}
/**
* Get users.
*
* #param int $node_id
* Node id.
*
* #return mixed
* #throws \GuzzleHttp\Exception\GuzzleException
*/
public function getUsers($node_id)
{
// API code goes here.
}
/**
* Get day.
*
* #return false|int|string
*/
public function getDay()
{
return $this->date->format('d');
}
}
class MyNotificationsTest extends TestCase
{
use RefreshMigrations;
public function testOneDay()
{
$mock = $this->getMockBuilder(MyNotifications::class)->onlyMethods([
'getUsers',
])->getMock();
$mock->method('getUsers')->willReturn(['User 1']);
dump($mock->getDay());
dump($mock->getUsers(1));
}
}
For example, I would like to pass the date "2021-12-22" to the constructor so the getDay() method returns 22 instead of the current day.

I haven't used PHPUnit mocks before (usually defaulting to Mockery) but looking at the documentation are you able to call setConstructorArgs(array $args) on the getMockBuilder?
class MyNotificationsTest extends TestCase
{
use RefreshMigrations;
public function testOneDay()
{
$mock = $this->getMockBuilder(MyNotifications::class)
->onlyMethods([
'getUsers',
])
->setConstructorArgs(['2021-03-08'])
->getMock();
$mock->method('getUsers')->willReturn(['User 1']);
dump($mock->getDay());
dump($mock->getUsers(1));
}
}

Related

Laravel: Stop using UUID as foreign key

Laravel is trying to use uuid field as foreign key. And I want to use foreign key with the field id. Is there any option there?
Using this trait on Model. And then it is trying to use the uuid as foreign key. But still I want to use id as foreign key.
<?php
namespace App\Library;
use Ramsey\Uuid\Uuid;
trait UsesUuid
{
/**
* #return string
*/
public function getKeyName()
{
return 'uuid';
}
/**
* #return string
*/
public function getKeyType()
{
return 'string';
}
/**
* #return false
*/
public function getIncrementing()
{
return false;
}
/**
* #param $query
* #param $uuid
* #return mixed
*/
public function scopeUuid($query, $uuid)
{
return $query->where($this->getUuidName(), $uuid);
}
/**
* #return string
*/
public function getUuidName()
{
return property_exists($this, 'uuidName') ? $this->uuidName : 'uuid';
}
/**
* #return string
*/
public function getRouteKeyName()
{
return property_exists($this, 'uuidName') ? $this->uuidName : 'uuid';
}
/**
*
*/
protected static function boot()
{
parent::boot();
static::creating(function ($model) {
$model->{$model->getUuidName()} = Uuid::uuid4()->toString();
});
}
}
There is nothing special about this trait. You can make your own trait with id instead of uuid and everything will work fine.
The issue came from methods getIncrementing() and getKeyName(). Laravel calls getKeyName() in amount of built-in functions to interacts with relationships, also other actions like delete(), route bindings, etc.
You should allow any models which uses this trait to custom the Primary key (PK), so that uuid is only common column at all.
Your trait definition is force PK column as uuid.
Below is my recommended for the trait
<?php
namespace App\Library;
use Ramsey\Uuid\Uuid;
trait UsesUuid
{
/* Override this method to set `uuid` as PK */
public function isUuidAsPrimaryKey()
{
return false;
}
/**
* #return string
*/
public function getKeyName()
{
return $this->isUuidAsPrimaryKey() ? $this->getUuidName() : $this->primaryKey;
}
/**
* #return string
*/
public function getKeyType()
{
return $this->isUuidAsPrimaryKey() ? 'string' : $this->keyType;
}
/**
* #return false
*/
public function getIncrementing()
{
return !$this->isUuidAsPrimaryKey();
}
/**
* #param $query
* #param $uuid
* #return mixed
*/
public function scopeUuid($query, $uuid)
{
return $query->where($this->getUuidName(), $uuid);
}
/**
* #return string
*/
public function getUuidName()
{
return property_exists($this, 'uuidName') ? $this->uuidName : 'uuid';
}
/**
* #return string
*/
public function getRouteKeyName()
{
return property_exists($this, 'uuidName') ? $this->uuidName : 'uuid';
}
/**
*
*/
protected static function boot()
{
parent::boot();
static::creating(function ($model) {
$model->{$model->getUuidName()} = Uuid::uuid4()->toString();
});
}
}
If a model need to have uuid as PK, for example Book model
class Book extends Model
{
use UsesUuid;
public function isUuidAsPrimaryKey()
{
return true;
}
}
Please recheck the method isUuidAsPrimaryKey. If it may not be overridden (due to conflict), then use a property instead.

Can i define parameters with Classes in Restler?

I want to declare params with differenc classes when i have a GET endpoint.
Example:
/**
* #param ClassA a
* #param ClassB b
...
public function getSomething() {}
public ClassA {
/**
* #param int
*/
public $i;
}
public ClassB {
...
}
Yes, using the class as a parameter works fine. See https://restler5.luracast.com/examples/_016_forms/readme.html Users class makes use of Address class
/**
* #param string $firstName
* #param string $lastName
* #param string $email
* #param string $password
* #param Address $address
*
* #return array
*
* #view users
*/
function postSignUp($firstName, $lastName, $email, $password, $address)
{
return func_get_args();
}
Address class is as follows
<?PHP
class Address
{
public $blockNumber;
public $streetName;
public $floor;
public $unitNumber;
public $country;
public $postalCode;
}

Model event is not triggered on laravel

I have a class that inherits a base class and uses a trait ... I will put the code below ..
The base class is using basically to do a validation before the rescue, using for this the saving event in the boot.
The trait is to tell the class to use uuid in the id attribute .. this trait uses the creating event of the boot.
In the class itself, the boot saving event is used to check if an active record exists.
In this code the trait creating event is not being triggered ... I can not do a save because uuid is not generated ... if I take the boot method in the final class the creating event is executed ...
something I'm not seeing ... does anybody have any idea what may be happening?
MAIN CLASS
class AcademicYear extends BaseModel
{
use UseUuid;
/**
* The "booting" method of the model.
*
* #return void
*/
protected static function boot()
{
parent::boot();
static::saving(function($model)
{
if($model->attributes['disable'] == false){
$model->searchActiveRecord();
}
});
}
public function searchActiveRecord(){
if ($this::where('disable', false)->count() >= 1){
throw new \App\Exceptions\OperationNotAllowed('operation not allowed', 'there is an active record', '422');
}
return true;
}
}
BASE MODEL
class BaseModel extends Model
{
/**
* If the model will be validated in saving
*
* #var bool
*/
protected static $validate = true;
/**
* Rules that will be used to validate the model
*
* #var array
*/
protected $validationRules = [];
/**
* Create a new base model instance.
*
* #param array $attributes
* #return void
*/
public function __construct($attributes = [])
{
parent::__construct($attributes);
}
/**
* The "booting" method of the model.
*
* #return void
*/
protected static function boot()
{
parent::boot();
static::saving(function($model)
{
if ($model::$validate) {
$model->validate();
}
});
}
/**
* Execute validation of model attributes.
*
* #return void
*/
public function validate()
{
$validator = Validator::make($this->attributesToArray(), $this->validationRules);
if($validator->fails()) {
throw new \App\Exceptions\OperationNotAllowed('validation failed', $validator->messages(), '422');
}
return true;
}
}
TRAIT
trait UseUuid
{
/**
* The "booting" method of the model.
*
* #return void
*/
protected static function boot()
{
parent::boot();
static::creating(function ($model)
{
$model->incrementing = false;
$model->keyType = 'string';
$model->{$model->getKeyName()} = Str::uuid()->toString();
});
static::retrieved(function ($model)
{
$model->incrementing = false;
});
}
}
Your model's boot method is conflicting with the trait's boot method, because they have the same name.
From the PHP.net manual on Traits:
An inherited member from a base class is overridden by a member inserted by a Trait. The precedence order is that members from the current class override Trait methods, which in turn override inherited methods.
Current class: AcademicYear
Trait: UseUuid
Inherited class: BaseModel
If you want to use a boot method on an individual model, you'll have to alias the trait's method to something different:
class AcademicYear extends BaseModel
{
use UseUuid {
boot as uuidBoot;
}
// ...
protected static function boot()
{
static::uuidBoot();
// Your model-specific boot code here.
}
}
Be careful with where you place parent::boot(). If you call parent::boot() in both your trait and your model, BaseModel::boot() will be called more than once.

How can I create a controller constructor in Laravel that takes in two concrete implementations of the same interface?

Background
Note: this is using Laravel 5.3, Please don't judge.
We are trying to use dependency injection with our laravel controllers and push as much business logic into repos that are injected to controllers upon the controller instantiation.
We already have this functioning example:
class AcmeController extends Controller
{
protected $repository;
public function __construct(AcmeInterface $repository)
{
$this->repository = $repository;
}
}
inside app/Providers/RepositoryServiceProvider.php we do the binding:
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class RepositoryServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
$this->app->bind(\App\Repositories\Contracts\AcmeInterface::class, \App\Repositories\OpCity\AcmeRepo::class);
}
}
and then the AcmeRepo naturally implements the AcmeInterface:
class AcmeRepo implements AcmeInterface
Question
right now we have a case where some of the data of the same model is persisted in a memory type storage (redis) and the rest is persisted in relational db storage (psql). We would like to have two separate repos where each repo is specific to its storage type, ie RedisAcmeRepo and SqlAcmeRepo
How is it possible to do this in the AcmeController constructor?
public function __construct(AcmeInterface $sqlRepo, AcmeInterface $redisRepo)
{
$this->sqlRepo = $sqlRepo;
$this->redisRepo = $redisRepo;
}
For example you may do this:
$this->app->bind(AcmeController::class, function ($app) {
return new AcmeController($app->make(sqlRepo::class), $app->make(redisRepo::class));
});
Or this:
$this->app->when(AcmeController::class)
->needs('$sqlRepo')
->give($app->make(sqlRepo::class));
$this->app->when(AcmeController::class)
->needs('$redisRepo')
->give($app->make(redisRepo::class));
based on the answers above I came up with this solution, that kind of uses the composite pattern as well (I changed the name of the repos from Acme to ShopperLogs):
<?php
interface ShopperLogInterface
{
public function getLogs($from, $to, $shopper);
}
class ShopperLogsController extends Controller
{
/**
* service
*
* #var \App\Repositories\Contracts\ShopperLogInterface
* #access protected
*/
protected $manager;
public function __construct(ShopperLogInterface $manager)
{
$this->manager = $manager;
}
}
class ShopperLogManager implements ShopperLogInterface
{
protected $sqlRepo;
protected $redisRepo;
public function __construct(ShopperLogInterface $sqlRepo, ShopperLogInterface $redisRepo)
{
$this->sqlRepo = $sqlRepo;
$this->redisRepo = $redisRepo;
}
public function getLogs($from, $to, $shopper)
{
$todayRange = //get the today part of from -- to
/**
* array of ShopperLogs
*/
$todaysLogs;
if ($todayRange) {
$this->redisRepo->getLogs($todayRange->start, $todayRange->finish, $shopper);
}
$legacyRange = //get the part of from -- to that excludes today's range
/**
* array of ShopperLogs
*/
$legacyLogs;
if ($legacyLogs) {
$this->sqlRepo->getLogs($todayRange->start, $todayRange->finish, $shopper);
}
return merge($todayRange, $legacyRange);
}
}
class ShopperLogsSqlRepo implements ShopperLogInterface
{
/**
* #var /Illuminate\Database\Eloquent\Model/ShopperLogs
*/
protected $model;
/**
* #param /Illuminate\Database\Eloquent\Model/ShopperLogs $model
*/
public function __construct(ShopperLogs $model)
{
$this->model = $model;
}
public function getLogs($from, $to, $shopper)
{
$this->model->whereLogs //do eloquent sql stuff here
}
}
class ShopperLogsRedisRepo implements ShopperLogInterface
{
/**
* #var \Redis\Model\Class
*/
protected $model;
/**
* #param \Redis\Model\Class $model
*/
public function __construct(ShopperLogs $model)
{
$this->model = $model;
}
public function getLogs($from, $to, $shopper)
{
$this->model->whereLogs //do redis stuff
}
}
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class RepositoryServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
$this->app->bind(\App\Repositories\Contracts\ShopperLogInterface::class, \App\Managers\ShopperLogManager::class);
$this->app->bind(ShopperLogsController::class, function ($app) {
return new ShopperLogsController($app->make(ShopperLogManager::class));
});
$this->app->bind(\App\Repositories\Contracts\ShopperLogInterface::class, function() {
return new \App\Managers\ShopperLogManager(new \App\Repositories\ShopperLogsSqlRepo(new \App\ShopperLog), new \App\Repositories\ShopperLogsRedisRepo(new \App\ShopperLog));
});
}
}

How to make text field compulsory only when the checkbox is checked with symfony annotation validations in model classes

It is probably a very simple question but I've run out of juice here. Vat field is compulsory only when isVatable checkbox is check by user otherwise it can be ignored. How do I achieve this with group validation (annotations) in model class, not entity?
I checked Validation Groups and Group Sequence but to be honest didn't get my head around.
FormType
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options = [])
{
$builder
->setMethod($options['method'])
->setAction($options['action'])
->add('vat', 'text')
->add('isVatable', 'checkbox')
;
}
public function getName()
{
return 'user';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(
['data_class' => 'My\FrontendBundle\Model\UserModel']
);
}
}
ModelClass
class UserModel
{
/**
* #Assert\NotBlank(message="Vat is required only when checkbox is checked.")
*/
protected $vat;
/**
* #var bool
*/
protected $isVatable = false;
}
I find that the #Assert\True() constraint on a method usually works well for me for these sorts of validation scenario. You can add some validation constraints to methods as well as properties, which is pretty powerful.
The basic idea is that you can create a method, give it this annotation - if the method returns true the validation passes; if it returns false it fails.
class UserModel
{
/**
* #var string
*/
protected $vat;
/**
* #var bool
*/
protected $isVatable = false;
/**
* #Assert\True(message="Please enter a VAT number")
*/
public function isVatSetWhenIsVatableChecked()
{
// if this property is unchecked we don't
// want to do any validation so return true
if (!$this->isVatable) {
return true;
}
// return true if $this->vat is not null
// you might want to add some additional
// validation here to make sure the
return !is_null($this->vat);
}
}
Additionally, you can map the error message to a specific form field with the error_mapping option in your FormType object, as documented here:
http://symfony.com/blog/form-goodness-in-symfony-2-1#error-mapping-fu
Hope this helps :)
Perhaps something like this in ...\Validator\Constraints:
VAT.php
use Symfony\Component\Validator\Constraint;
class VAT extends Constraint
{
public $message = 'VAT is compulsory for applicable items';
public $vat;
public $isVatable;
}
VATConstraint.php
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\PropertyAccess\PropertyAccess;
class VATValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
$accessor = PropertyAccess::createPropertyAccessor();
$data = $accessor->getValue($this->context->getRoot(), 'data');
$vat = $data['vat'];
$isVatable = $data['isVatable'];
if ($isVatable && empty($vat)) {
$this->context->addViolation($constraint->message, array('%string%' => $value));
return false;
}
return true;
}
}
User model Entity:
use YourBundle\Validator\Constraints as MyAssert;
class UserModel
{
/**
* #MyAssert\NotBlank(message="Vat is required only when checkbox is checked.")
*/
protected $vat;
...
}
You should use a Class Constraint Validator
First make your Constraint class and your validatorClass:
<?php
// AppBundle/Validator/Constraints/NotBlankIfTaxEnabled.php
namespace AppBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class NotBlankIfTaxEnabled extends Constraint
{
public $message = 'If isVat is enabled you have to enter a value in the Vat field.';
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
}
and
<?php
// AppBundle/Validator/Constraints/NotBlankIfTaxEnabledValidator.php
namespace AppBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class NotBlankIfTaxEnabledValidator extends ConstraintValidator
{
public function validate($customer, Constraint $constraint)
{
if($customer->getIsVatable() && strlen($customer->getVat()) == 0)
{
$this->context->buildViolation($constraint->message)
->addViolation();
}
}
}
Then add de classcontraint to your Entity/Model
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use AppBundle\Validator\Constraints as AppAssert;
/**
* Customer
*
* #ORM\Table()
* #ORM\Entity
* #AppAssert\NotBlankIfTaxEnabled
*/
class Customer
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="description", type="string", length=64)
*
*/
private $vat;
/**
* #var boolean
*
* #ORM\Column(name="taxEnabled", type="boolean")
*/
private $isVatable;
and do not forget to switch off the required attribute for both fields in your formType:
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class CustomerType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('vat', NULL, array('required' => FALSE))
->add('isVatable', NULL, array('required' => FALSE))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Customer'
));
}
/**
* #return string
*/
public function getName()
{
return 'appbundle_customer';
}
}

Resources