PHPStan Extension: Dynamic Return Types and a Variadic Function Parameter - static-analysis

Since PHPStan 1.6, it's possible to use Conditional Return Types, where I've been able to do things like:
/**
* #param string $x
* #return ($x is literal-string ? literal-string : string)
*/
public function isNull($x)
{
}
This takes the form of (<template param> is <union type> ? <union type> : <union type>).
While it's not possible to do more complicated conditions, it is possible to nest them (even if it gets a bit messy):
/**
* #param string $val
* #param string $x
* #param string $y
* #return ($val is literal-string ? ($x is literal-string ? ($y is literal-string ? literal-string : string) : string) : string)
*/
public function between($val, $x, $y)
{
}
But I'm not sure how to handle a Variadic Function Parameter, where the function can accept any number of values.
I'd like to return a literal-string when all values are a literal-string, otherwise return a string.
Maybe something like the following (which does not work):
/**
* #param string ...$x
* #return ($x is array<literal-string> ? literal-string : string)
*/
function example(...$x) {
return implode(', ', $x);
}
Is this a limitation of the current implementation in PHPStan, or am I missing something?
This relates to the PHPStan Doctrine Extension, and Pull Request 324.
One option is to use a Dynamic Return Type Extension (which I might revert).

The solution is to use a #template, e.g.
/**
* #template T of string
* #param T ...$x
* #return (T is literal-string ? literal-string : string)
*/
public function countDistinct(...$x) {
}
But this does not work with Doctrine ORM 2.x, because it uses ($x) not (...$x) for its arguments (source), which PR 9911 fixes for 3.x.

Related

Doctrine ArrayCollection matching criteria function results in Undefined property: MyEntity::$1 (Symfony 3.4)

I defined an Entity "TrainingProgressEntry" as an #ORM\Entity and a "training" property like this:
/**
* #ORM\ManyToOne(targetEntity="Training", inversedBy="training_progress")
* #ORM\JoinColumn(name="training_id", referencedColumnName="id")
*/
protected $training;
The matching #ORM\Entity "Training" defines a property "training_progress" like
/**
* #ORM\OneToMany(targetEntity="TrainingProgressEntry", mappedBy="training", cascade={"remove"})
* #ORM\OrderBy({"entry_date" = "ASC"})
*/
protected $training_progress;
and a getter method for it like this
/**
* Get trainingProgress
*
* #return \Doctrine\Common\Collections\ArrayCollection
*/
public function getTrainingProgress()
{
return $this->training_progress;
}
Finaly I define a getter method intended to return only entries which have a date newer then some reference date:
/**
* #return \Doctrine\Common\Collections\ArrayCollection
*/
public function getTrainingProgressSinceStart()
{
$startTime = $this->getUser()->getStart();
$criteria = Criteria::create()
->andWhere(Criteria::expr()->gt('entry_date', $startTime))
->orderBy(['entry_date', 'ASC']);
return $this->getTrainingProgress()->matching($criteria);
}
When using this last function I get the following "ContextErrorException":
Notice: Undefined property: AppBundle\Entity\TrainingProgressEntry::$1
coming from
vendor\doctrine\collections\lib\Doctrine\Common\Collections\Expr\ClosureExpressionVisitor.php
when trying to "return $object->$field".
The trace shows that it is caused by the above mentioned function "getTrainingProgressSinceStart()" in the line
return $this->getTrainingProgress()->matching($criteria);
For some reason the matching function doesn't seem to be recognized ors something...
I don't really know what to look for now.
Any hints are very welcome.
You probably already solved this, but I will answer it either way for reference for other people.
The method: orderBy of criteria accepts an array with the Key, being the field and the sorting order being the value, so where you have:
/**
* #return \Doctrine\Common\Collections\ArrayCollection
*/
public function getTrainingProgressSinceStart()
{
$startTime = $this->getUser()->getStart();
$criteria = Criteria::create()
->andWhere(Criteria::expr()->gt('entry_date', $startTime))
->orderBy(['entry_date', 'ASC']);
return $this->getTrainingProgress()->matching($criteria);
}
It should really be ['entry_date' => 'ASC']:
/**
* #return \Doctrine\Common\Collections\ArrayCollection
*/
public function getTrainingProgressSinceStart()
{
$startTime = $this->getUser()->getStart();
$criteria = Criteria::create()
->andWhere(Criteria::expr()->gt('entry_date', $startTime))
->orderBy(['entry_date' => 'ASC']);
return $this->getTrainingProgress()->matching($criteria);
}
Source: https://github.com/doctrine/collections/blob/c23e14f69b6d2d1d1e389bc8868500efc447af7b/lib/Doctrine/Common/Collections/Criteria.php#L152

Laravel 5.6 Hash

I know the question was already ask but i have tried without success.
We have an old database with SHA256 and double salt and we wan't to use this for Register and Login.
I have follow this tutorial and many more : https://conceptsandimplementation.wordpress.com/2017/03/07/replace-laravels-default-password-hash-bcrypt-with-base64-encode/
This my code :
namespace App\Libs\CustomHash;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
class CustomHasher implements HasherContract {
public function info($hashedValue) {
}
/**
* Hash the given value.
*
* #param string $value
* #return array $options
* #return string
*/
public function make($value, array $options = array()) {
$PasswordHashed = 'a5df5z' . $value . 'a45ee1a';
$PasswordHashed = hash('sha256', $value);
return $PasswordHashed;
}
/**
* Check the given plain value against a hash.
*
* #param string $value
* #param string $hashedValue
* #param array $options
* #return bool
*/
public function check($value, $hashedValue, array $options = array()) {
return $this->make($value) === $hashedValue;
}
/**
* Check if the given hash has been hashed using the given options.
*
* #param string $hashedValue
* #param array $options
* #return bool
*/
public function needsRehash($hashedValue, array $options = array()) {
return false;
}
}
And :
namespace App\Providers;
use Illuminate\Hashing\HashServiceProvider;
use App\Libs\CustomHash\CustomHasher as CustomHasher;
class CustomHashServiceProvider extends HashServiceProvider
{
public function register()
{
$this->app->singleton('hash', function () {
return new CustomHasher;
});
}
}
Here my providers list
//Illuminate\Hashing\HashServiceProvider::class,
App\Providers\CustomHashServiceProvider::class,
I know that's not a good practive to use SHA but i do not have the choice.
Thank's in advance for you're help.

What is the best way for reusable values throughout the application in Symfony 3?

I want to have a file or list that I can update easily with values that might change throughout my application.
I don't really want to hard code text values into the templates. I prefer to have all of these values in one place and labelled correctly.
Examples of values that might get updated are:
Page title
Logo text
Brand or company name
I have thought about two options:
Add them to the twig config in config.yml. This is a bit messy and doesn't seem organised if I decide to put a lot of values there.
Make a database table for these and include the entity in each controller where I need to use the values. This might be creating too much work.
Are there any other options or are one of these more suitable?
Thank you.
You need to create a twig function and use it to return the value you want. For example:
namespace AppBundle\Twig;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
class TwigExtension extends \Twig_Extension implements ContainerAwareInterface
{
use ContainerAwareTrait;
/**
* #var ContainerInterface
*/
protected $container;
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('parameter', function($name)
{
try {
return $this->container->getParameter($name);
} catch(\Exception $exception) {
return "";
}
})
);
}
/**
* Returns the name of the extension.
*
* #return string The extension name
*/
public function getName()
{
return 'app.twig.extension';
}
}
This will create a function called parameter and once you call it in twig {{ parameter('my.parameter') }} it will return the parameter. You need to load it as a service, which you can do by adding the following to your services.yml file:
app.twig.extension:
class: AppBundle\Twig\TwigExtension
calls:
- [setContainer, ["#service_container"]]
tags:
- { name: twig.extension }
From personal experience people usually want to be able to change some of the parameters. This is why I usually prefer to create a Setting or Parameter entity which would look something like this:
/**
* Setting
*
* #ORM\Table(name="my_parameters")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ParameterRepository")
*/
class Parameter
{
/**
* #var integer
*
* #ORM\Id
* #ORM\Column(name="parameter_id", type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="value", type="text", nullable=true)
*/
private $value;
/**
* #param string|null $name
* #param string|null $value
*/
public function __construct($name = null, $value = null)
{
$this->setName($name);
$this->setValue($value);
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Parameter
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set value
*
* #param string $value
*
* #return Parameter
*/
public function setValue($value = null)
{
$this->value = serialize($value);
return $this;
}
/**
* Get value
*
* #return string
*/
public function getValue()
{
$data = #unserialize($this->value);
return $this->value === 'b:0;' || $data !== false ? $this->value = $data : null;
}
}
Then I would add a CompilerPass which will help get all of the parameters from the database and cache them so that your app doesn't make unnecessary sql queries to the database. That might look something similar to the following class:
// AppBundle/DependencyInjection/Compiler/ParamsCompilerPass.php
namespace AppBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class ParamsCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$em = $container->get('doctrine.orm.default_entity_manager');
$settings = $em->getRepository('AppBundle:Parameter')->findAll();
foreach($settings as $setting) {
// I like to prefix the parameters with "app."
// to avoid any collision with existing parameters.
$container->setParameter('app.'.strtolower($setting->getName()), $setting->getValue());
}
}
}
And finally, in your bundle class (i.e. src/AppBundle/AppBundle.php) you add the compiler pass:
namespace AppBundle;
use AppBundle\DependencyInjection\Compiler\ParamsCompilerPass;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class AppBundle extends Bundle
{
public function build(ContainerBuilder $builder)
{
parent::build($builder);
$builder->addCompilerPass(new ParamsCompilerPass(), , PassConfig::TYPE_AFTER_REMOVING);
}
}
Now you can create a DoctrineFixture template to load the parameters you use all the time. With the TwigExtension you will still be able to call the parameter from the twig template and you can create a web UI to change some of the parameters/settings.

codeigniter + require letters and numbers in password

I'd like to use form validation to require a password that has BOTH alpha and numeric characters. Here's what I've come up with so far:
$this->form_validation->set_rules('password', 'Password', 'required|matches[passconf]|min_length[8]|alpha_numeric');
The issue is that "alpha_numeric" requires that the password only contain letters or numbers, it doesn't require both. Going for the stronger password option here.
You could set up a callback in your controller:
public function password_check($str)
{
if (preg_match('#[0-9]#', $str) && preg_match('#[a-zA-Z]#', $str)) {
return TRUE;
}
return FALSE;
}
Then, update your rule to use it:
$this->form_validation->set_rules('password', 'Password', 'required|matches[passconf]|min_length[8]|alpha_numeric|callback_password_check');
An alternate solution that can be easily reused in other projects and localizes your validation rules is to extend the Form_validation class.
In application/libraries/ create a file called MY_Form_validation.php where 'MY' is your subclass prefix set in application/config/config.php.
The class would look something like:
class MY_Form_validation extends CI_Form_validation
{
/**
* Verify that a string contains a specified number of
* uppercase, lowercase, and numbers.
*
* #access public
*
* #param String $str
* #param String $format
*
* #return int
*/
public function password_check($str, $format)
{
$ret = TRUE;
list($uppercase, $lowercase, $number) = explode(',', $format);
$str_uc = $this->count_uppercase($str);
$str_lc = $this->count_lowercase($str);
$str_num = $this->count_numbers($str);
if ($str_uc < $uppercase) // lacking uppercase characters
{
$ret = FALSE;
$this->set_message('password_check', 'Password must contain at least ' . $uppercase . ' uppercase characters.');
}
elseif ($str_lc < $lowercase) // lacking lowercase characters
{
$ret = FALSE;
$this->set_message('password_check', 'Password must contain at least ' . $lowercase . ' lowercase characters.');
}
elseif ($str_num < $number) // lacking numbers
{
$ret = FALSE;
$this->set_message('password_check', 'Password must contain at least ' . $number . ' numbers characters.');
}
return $ret;
}
/**
* count the number of times an expression appears in a string
*
* #access private
*
* #param String $str
* #param String $exp
*
* #return int
*/
private function count_occurrences($str, $exp)
{
$match = array();
preg_match_all($exp, $str, $match);
return count($match[0]);
}
/**
* count the number of lowercase characters in a string
*
* #access private
*
* #param String $str
*
* #return int
*/
private function count_lowercase($str)
{
return $this->count_occurrences($str, '/[a-z]/');
}
/**
* count the number of uppercase characters in a string
*
* #access private
*
* #param String $str
*
* #return int
*/
private function count_uppercase($str)
{
return $this->count_occurrences($str, '/[A-Z]/');
}
/**
* count the number of numbers characters in a string
*
* #access private
*
* #param String $str
*
* #return int
*/
private function count_numbers($str)
{
return $this->count_occurrences($str, '/[0-9]/');
}
}
Then set the rule:
$this->form_validation->set_rules('password', 'Password', 'required|matches[passconf]|min_length[8]|alpha_numeric|password_check[1,1,1]');
The rule can be used in many different ways. 'password_check[1,1,1]' would require a password contain a lowercase, uppercase, and number character. 'password_check[5,0,1]' would require 5 uppercase characters and a number in the password.
The advantages of doing it this way are:
All your rules are contained within an easily portable class
Sub-validation functions such as 'count_occurences' can easily be used in other validation functions.
The class can be modified to use language files in application/language/
Using CI 2.2.0, there are parameters you can add, like the 'alpha_numeric' parameter, note the piped (cascaded) parameters:
function check_password(){
$this -> form_validation -> set_rules('password', 'New Password', 'required|max_length[15]|min_length[6]|alpha_numeric');
$this -> form_validation -> set_rules('passconf', 'Re-enter', 'required|matches[password]');
if ($this -> form_validation -> run() == FALSE){
doStuff();
} else {
doOtherStuff();
}
}
Look for the Rule Reference near the bottom of this page.

How can I validate a "does not match" type using CodeIgniter's Form Validation class?

I want to ensure that a certain text field does not contain a specific value. Is there any way for me to do this using CI's Form Validation class or do I have to write my own extension for it?
I would extend the form validation class:
http://codeigniter.com/user_guide/general/creating_libraries.html
Something like
<?
class MY_Form_validation extends CI_Form_validation {
function __constuct() {
parent::__constuct();
}
function isnt($str,$field){
$this->CI->form_validation->set_message('isnt', "%s contains an invalid response");
return $str!==$field;
}
}
?>
Your validation rule would be something like
trim|alpha_numeric|isnt[invalid value]
Or, you can create a callback function instead of extending the class. The form validation section of the CI user guide has a relevant example:
http://codeigniter.com/user_guide/libraries/form_validation.html#callbacks
I agree with Billiam that you should extend the Form_validation class
I find it is more likely that one would want to validate a white-list of possible string values rather than a black-list. For example, you know your 'shirt_size' field should only return the string values: 'xl','l','m','s'. My solution is to handle both cases.
I use these methods in MY_From_validation:
/**
* ENUM
* The submitted string must match one of the values given
*
* usage:
* enum[value_1, value_2, value_n]
*
* example (any value beside exactly 'ASC' or 'DESC' are invalid):
* $rule['order_by'] = "required|enum[ASC,DESC]";
*
* example of case-insenstive enum using strtolower as validation rule
* $rule['favorite_corey'] = "required|strtolower|enum[feldman]";
*
* #access public
* #param string $str the input to validate
* #param string $val a comma separated lists of values
* #return bool
*/
function enum($str, $val='')
{
if (empty($val))
{
return FALSE;
}
$arr = explode(',', $val);
$array = array();
foreach($arr as $value)
{
$array[] = trim($value);
}
return (in_array(trim($str), $array)) ? TRUE : FALSE;
}
// --------------------------------------------------------------------
/**
* NOT ENUM
* The submitted string must NOT match one of the values given
*
* usage:
* enum[value_1, value_2, value_n]
*
* example (any input beside exactly 'feldman' or 'haim' are valid):
* $rule['favorite_corey'] = "required|not_enum['feldman','haim']";
*
* #access public
* #param string $str the input to validate
* #param string $val a comma separated lists of values
* #return bool
*/
function not_enum($str, $val='')
{
return ($this->enum($str,$val) === TRUE)? FALSE : TRUE;
}
Using Billiam's example, the validation rule not allow string 'invalid value' would be something like:
trim|alpha_numeric|not_enum[invalid value]
Actually, there's a fairly simple example given for this very question in the User Guide - for V2 or V3
Look for the section "Callbacks: Your own Validation Functions". In the example it uses a check for the word "test" in the username field, and returns the custom error if the value is found.
In your controller, change the "username" rule to this:
$this->form_validation->set_rules('username', 'Username', 'callback_username_check');
Then add a new function called username_check to your controller:
function username_check($str)
{
if ($str == 'test')
{
$this->form_validation->set_message('username_check', 'The %s field can not be the word "test"');
return FALSE;
}
else
{
return TRUE;
}
}
And Bob's your uncle...
CodeIgniter's form validation class can call almost any declared PHP function in your rule set. So I would simply declare a function like so:
class yourController {
function someFunction() {
$this->form_validation->set_rules('the_field_you_want_to_check', 'The Field Name', 'trim|myvalfunc[not this value]|xss');
}
}
function myvalfunc($formvalue, $notallowed) {
$this->CI->form_validation->set_message('myvalfunc', "%s is not allowed");
return $formvalue !== $nowallowed;
}

Resources