Pass optional arrgument Laravel 5.8, cron job - php

protected $signature = 'do_command {import=false}';
public function handle(){
$import= $this->argument('import');
if($import){
// do something else
}
}
and I am using it in the controller and NOT only:
$command = 'do_command';
$option = null;
if($import){
$option = 'import';
}
Artisan::call($command, [$option]);
The problem is, it doesn't matter, if $import in the controller is true/false, if statement is always executed and $this->argument('import') is always true in handle method, even if I call Artisan::call($command) without second argument.

You are defining the default value of import as false, which would be a string. Therefore, the if condition in your command will always be true:
if ($import) {
//
}
What you could do is change the signature to have the import option as optional.
protected $signature = 'command:name {import?}';
Then in your controller:
Artisan::call($command, [
'import' => $import,
]);

First define the options as parameters.
class GenerateApiToken extends Command
{
protected $signature = "apitoken:generate
{--id= : A description of the option}
{--value= : A description of the option}
";
public function handle()
{
$id = $this->option('id');
$value = $this->option('value');
...
Then grab them using $this->option()
To use them in a call from the application:
Artisan::call('apitoken:generate', ['id' => $id]);
Edit: I think the reason yours doesn't work is because the array you're passing the command in the second parameter of ::call() is not an associative array with the keynames representing options.

Related

Mocking data and Calling action from container

I'm having issues making a Unit Testing because I have this code helperController Controller:
public static function applyDiscount(array $params, MoneyParser $moneyParser): Money
{
$discount = Money::BRL(0);
switch ($params['typeOf']) {
case 'above_value':
$amount = $params['value']->subtract($params['value']->multiply(0.85));
$discount = $discount->add($amount);
break;
case 'above_quantity':
$subtractedQuantity = intval(round($params['quantity'] / 3));
$value = $params['value']->multiply($subtractedQuantity);
$discount = $discount->add($value);
break;
case 'same_category':
$lowestVal = 0;
foreach ($params['cart'] as $item) {
if (empty($lowestVal)) {
$lowestVal = $item['unitPrice'];
} elseif ($item['unitPrice'] < $lowestVal) {
$lowestVal = $item['unitPrice'];
}
}
$valueOf = floatval($lowestVal) * 0.40;
$rounded = floor($valueOf * 100) / 100;
$unitPrice = $moneyParser->parse(strval($rounded), 'BRL');
$discount = $discount->add($unitPrice);
break;
case 'for_employees':
$amount = $params['value']->subtract($params['value']->multiply(0.80));
$discount = $discount->add($amount);
break;
case 'for_newones':
$amount = $moneyParser->parse('25', 'BRL');
$discount = $discount->add($amount);
break;
}
return $discount;
}
And I'm trying to mock the data to test it, for example, in the first case I'm trying to do this:
public function testApplyDiscountAboveValue(): void
{
$params = [
'typeOf' => 'above_value',
'value' => Money::BRL('3001'),
];
$money = app(MoneyParser::class);
$currency = app(Currency::class, ['code' => 'BRL']);
$moneyValue = app(Money::class, [
'amount' => 3001,
'currency' => $currency
]);
$discount = app(Money::class, [
'amount' => 450,
'currency' => $currency
]);
$mock = Mockery::mock($moneyValue);
$mock->shouldReceive('multiply')
->with(0.85)
->once()
->andReturn(Money::BRL('2550'));
$mock->shouldReceive('subtract')
->with(Money::BRL('2550'))
->once()
->andReturn(Money::BRL('450'));
$mock->shouldReceive('add')
->with(Money::BRL('450'))
->once()
->andReturn(Money::BRL('450'));
$response = app(HelperDiscountController::class)->applyDiscount($params, $money);
$this->assertEquals($discount, $response);
}
When I execute the phpunit my result is this:
App\Http\Controllers\HelperDiscountControllerTest::testApplyDiscountAboveValue
Mockery\Exception\InvalidCountException: Method multiply(0.85) from Mockery_0_Money_Money_Money_Money should be called
exactly 1 times but called 0 times.
I'm trying to understand what I am doing wrong. Did I have all the necessary adaptations to test my code at least for the first case?
First of all, if you have a static method in a controller, you are doing something wrong, it should be a non-static method, like this:
public function applyDiscount(array $params, MoneyParser $moneyParser): Money
{
// ...
}
Second, if the method applyDiscount is a helper method inside a controller or a parent controller, I would not pass an array $params, but directly a Request $request and use that object, because the array could have missing indexes and you should constantly validate them, with a Request object you can directly do $request->has('param') or do $request->input('param') (and pass a second parameter if you want a default value different from null if param is not present on the request as a parameter).
I do see that, for example, $params['value'] is an object, because you are doing operations on it, so if $params is not a parameter (you will never get an object from a Request as a parameter) then rename the variable to something else that truly repesent what it has, or at least, pass the content as separte method's parameters instead of an array of objects or similar.
Third, now onto the test. $response = app(HelperDiscountController::class)->applyDiscount($params, $money); will never work (get the mock), because you are never passing that mock into the Service Cointaner or into an instance as a dependency, you are creating the mock and not using it anywhere.
The code is a little bit confusing for me right now, but what you should have is something like this:
Controller
public function applyDiscount(Request $request, ): Money
{
// Validate or use a Form Request
// Do anything else you would need to
$result = $this->helperMethod(... needed params);
// Either return the result or process more stuff and return something if needed
return $result;
}
Test
public function testApplyDiscountAboveValue(): void
{
// When you need to mock something, you do it this way
$mock = $this->mock(WhateverClass::class);
$mock->shouldReceive('multiply')
->with(0.85)
->once()
->andReturn(Money::BRL('2550'));
$mock->shouldReceive('subtract')
->with(Money::BRL('2550'))
->once()
->andReturn(Money::BRL('450'));
$mock->shouldReceive('add')
->with(Money::BRL('450'))
->once()
->andReturn(Money::BRL('450'));
$response = $this->post(
'your/url/to/the/controller/endpoint',
[
'param1' => 'data1',
'paramN' => 'dataN',
],
);
$this->assertEquals($discount, $response);
}
See that:
When you use $this->mock(Class), it is internally binding that class resolution (when you do app(class) or resolve(class) or you let Laravel use Dependency Injection), it will return that mock
I am calling an endpoint using $this->post(url, data), you can use any HTTP method, but must match the route (that is how you test routes)
You need to share more info becausue it is very confusing (for me) what you are trying to test
Check my StackOverflow profile, I have some links in my profile about testing, it will help you a lot
Check the documentation as it is already explained in there most of the things you are trying to do
HTTP test
Mocking
Automatic Resolving/Dependency Injection

Add to Laravel query using callable

I want to add to a Laravel query using a callable.
For example, I have a string for sorting records. Based on this string, I want to add to my query via an array:
public $sort = 'Newest';
public function sortQuery(Builder $query)
{
return [
'Name' => $query->orderBy('name'),
'Newest' => $query->orderBy('created_at'),
'Oldest' => $query->orderByDesc('created_at'),
];
}
public function paginateQuery()
{
$query = User::query();
foreach ($this->sortQuery($query) as $key => $value) {
if ($this->sort == $key) {
$query = $value;
}
}
return $query->paginate();
}
In this example, when I run $this->paginateQuery() it does not sort as desired.
I've also tried $query = $this->sortQuery($query)[$this->sort]; instead of the foreach loop and the result is the same.
How would I chain $value onto the $query based on the array key?
You can amend your function slightly to apply the sorting immediately, for instance like so:
public function sortQuery(Builder $query, $sortKeys)
{
// Define a map to find the options for your specific sorting key.
$map = [
'Name' => ['name', 'ASC'],
// Note: I flipped this around, Newest first means "descending date".
'Newest' => ['created_at', 'DESC'],
'Oldest' => ['created_at', 'ASC'],
];
// Loop the given sortkeys. The (array) cast allows you to pass a string as well.
foreach((array) $sortKeys as $sortKey) {
// Check if map exists.
if(isset($map[$sortKey])) {
// Use the splat operator to pass the map values as arguments to the orderBy function
// (the second argument can be ASC/DESC)
$query->orderBy(...$map[$sortKey]);
}
}
You can even define it as a query scope in one of your models (preferably a parent class that is inherited by multiple Eloquent models), see https://laravel.com/docs/8.x/eloquent#local-scopes:
// Model.php
/**
* The prefix `scope` is required. It can be called as `$query->sortMe(...)`.
*/
public function scopeSortMe(Builder $query, $sortKeys)
{
// ... same code
}
// SomeController.php
// Calling it only requires the additional parameters, not the $query object.
$query->sortMe($sortKeys);

Passing a variable to a ->each() function making the variable always = 0 php

One of the routes I'm making for my API in laravel requires me to pass a variable to a ->each() function.
This can be seen below:
public function by_location($zone_id)
{
$zone = Zone::where('id', $zone_id)->get()[0];
error_log($zone->id);
$exhibitors = Exhibitor::where('zone_id', $zone_id)->get();
$exhibitors->each(function($exhibitor, $zone)
{
error_log($zone->id);
$exhibitor['zone_info'] = $zone;
});
return response()->json($exhibitors);
}
This first error_log outputs '2', but with the second I get 'Trying to get property 'id' of non-object'.
Any help is apprecited!
You probably want to use $zone which you selected from database on first line.
Also if you want to change value of item you are iterating you have to use ->map() instead of ->each()
I changed ->get()[0] to ->first(). Never use ->get()[0]
public function by_location($zone_id)
{
$zone = Zone::where('id', $zone_id)->first();
error_log($zone->id);
$exhibitors = Exhibitor::where('zone_id', $zone_id)->get();
$exhibitors->map(function($exhibitor) use ($zone){
error_log($zone->id);
$exhibitor['zone_info'] = $zone;
return $exhibitor;
});
return response()->json($exhibitors);
}

Optional parameters in PHP function without considering order

Is there a way in PHP to use a function which has optional parameters in its declaration where I do not have to pass an optional arguments which already have values declared and just pass the next argument(s) which have different values that are further down the parameter list.
Assuming I have a function that has 4 arguments, 2 mandatory, 2 optional. I don't want to use null values for the optional arguments. In usage, there are cases where I want to use the function and the value of the 3rd argument is the same as the default value but the value of the 4th argument is different.
I am looking for a not so verbose solution that allows me to just pass the argument that differs from the default value without considering the order in the function declaration.
createUrl($host, $path, $protocol='http', $port = 80) {
//doSomething
return $protocol.'://'.$host.':'.$port.'/'.$path;
}
I find myself repeating declaring variables so that I could use a function i.e to use $port, I redeclare $protocol with the default value outside the function scope i.e
$protocol = "http";
$port = 8080;
Is there any way to pass the 2nd optional parameter($port) without passing $protocol and it would "automatically" fill in the default value of $protocol i.e
getHttpUrl($server, $path, $port);
This is possible in some languages like Dart in the form of Named Optional parameters.See usage in this SO thread. Is their a similar solution in PHP
You could potentially use a variadic function for this.
Example:
<?php
function myFunc(...$args){
$sum = 0;
foreach ($args as $arg) {
$sum += $arg;
}
return $sum;
}
Documentation:
http://php.net/manual/en/functions.arguments.php#functions.variable-arg-list
PHP doesn't allow at this state to call functions parameters in the order we want.Maybe in the future it will.However you can easily achieve your purpose by using an associative array as the only argument, and then define, the default parameter in the function.For the call you will need to pass an array with only the values which interest you.This array will be merged with the default array.You can even implement required parameters and call them in any order you want.
example:
function mysweetcode($argument){
$required=['first'];//specify required parameters here
$default=['first'=>0,'second'=>1,'third'=>2];//define all parameters with their default values here
$missing=[];
if(!is_array($argument)) return false;
$argument=array_intersect_key($argument,$default);
foreach($required as $k=>$v){//check for missing required parameters
if(!isset($argument[$v]))
$missing[]=$v;
}
if(!empty($missing)){// if required are missing trigger or throw error according to the PHP version
$cm=count($missing);
if (version_compare(PHP_VERSION, '7.0.0') < 0) {
trigger_error(call_user_func_array('sprintf',
array_merge(array('Required '.(($cm>1)?'parameters:':'parameter:').
str_repeat('%s,',$cm).(($cm>1)?' are':' is').' missing'),$missing)),
E_USER_ERROR);
}else{
throw new Error(call_user_func_array('sprintf',array_merge(
array('Required '.(($cm>1)?'parameters:':'parameter:').
str_repeat('%s',$cm).(($cm>1)?' are':' is').' missing'),$missing)));
}
}
$default=array_merge($default,$argument);//assign given values to parameters
extract($default);/*extract the parameters to allow further checking
and other operations in the function or method*/
unset($required,$missing,$argument,$default,$k,$v);//gain some space
//then you can use $first,$second,$third in your code
return $first+$second+$third;
}
var_dump(mysweetcode(['first'=>9,'third'=>8]));//the output is 18
var_dump(mysweetcode(['third'=>8]));//this throws Error on PHP7 and trigger fatal error on PHP5
You can check a live working code here
Well, this should work:
function myFunc($arg1, $arg2, $arg3=null, $arg4= null){
if ( is_null( $arg3 ) && is_null( $arg4 ) {
$arg3 = 3;
$arg4 = 4;
} else if ( is_null( $arg4 ) ) {
$arg4 = $arg3;
$arg3 = 3;
}
echo $arg1 + $arg2 + $arg3 + $arg4;
}
However I suggest you to rethink your problem (as a whole) because this is not a very good idea.
You could refactor this to use a parameter object; this way, you could include the default parameters in this object and set them in any order (with a trade-off of more verbose code). As an example using your above code,
<?php
class AdditionParameters
{
private $arg1 = 0;
private $arg2 = 0;
private $arg3 = 3;
private $arg4 = 4;
public function getArg1() { return $this->arg1; }
public function getArg2() { return $this->arg2; }
public function getArg3() { return $this->arg3; }
public function getArg4() { return $this->arg4; }
public function setArg1($value) { $this->arg1 = $value; return $this; }
public function setArg2($value) { $this->arg2 = $value; return $this; }
public function setArg3($value) { $this->arg3 = $value; return $this; }
public function setArg4($value) { $this->arg4 = $value; return $this; }
}
From there, you could simply call the function while passing in this new object.
function myFunc(AdditionParameters $request) {
return $request->getArg1()
+ $request->getArg2()
+ $request->getArg3()
+ $request->getArg4();
}
echo myFunc((new AdditionParameters)->setArg1(1)->setArg2(2)->setArg4(6));
// or echo myFunc((new AdditionParameters)->setArg1(1)->setArg4(6)->setArg2(2));
Otherwise, PHP doesn't allow you to have named optional parameters. (e.g. myFunc(1, 2, DEFAULT, 4);)
You have the response in your question, you can declare your function like
function myFunc($arg1, $arg2, $arg3 = null, $arg4 = null){
//here you check if the $arg3 and $arg4 are null
}
then you call your function using
myFunc($arg1, $arg2);
There is no such way in PHP(like in python for example).
You have to use some tricks in order to do that but will not always work.
For example:
// creates instances of a class with $properties.
// if $times is bigger than 1 an array of instances will be returned instead.(this is just an example function)
function getInstance($class, $properties = [], $times = 1){
//my code
}
$user = getInstance("User", ["name" => "John"]); // get one instance
$users = getInstance("User", ["name" => "John"],2); // get two instances.
If you want to use the function without passing the $parameters argument, like this:
$users = getInstance("User",2);
you can change the function to:
// creates instances of a class with $properties.
// if times is bigger than 1 an array of instances will be returned instead.
function getInstance($class, $properties = [], $times = 1){
if(is_numberic($properties)){
$times = $properties;
$properties = [];
}
//my code
}
Of course, this strategy will work only if you parameters have different types.
PS. This method is use in the Laravel Framework a lot. From there I got the inspiration.
This is modified from one of the answers and allows arguments to be added in any order using associative arrays for the optional arguments
function createUrl($host, $path, $argument = []){
$optionalArgs = [
'protocol'=>'http',
'port'=>80];
if( !is_array ($argument) ) return false;
$argument = array_intersect_key($argument,$optionalArgs);
$optionalArgs = array_merge($optionalArgs,$argument);
extract($optionalArgs);
return $protocol.'://'.$host.':'.$port.'/'.$path;
}
//No arguments with function call
echo createUrl ("www.example.com",'no-arguments');
// returns http://www.example.com:80/no-arguments
$argList=['port'=>9000];
//using port argument only
echo createUrl ("www.example.com",'one-args', $argList);
//returns http://www.example.com:9000/one-args
//Use of both parameters as arguments. Order does not matter
$argList2 = ['port'=>8080,'protocol'=>'ftp'];
echo createUrl ("www.example.com",'two-args-no-order', $argList2);
//returns ftp://www.example.com:8080/two-args-no-order
As of version 8.0, PHP now has named arguments. If you name the arguments when calling the function, you can pass them in any order and you can skip earlier default values without having to explicitly pass a value for them.
For example:
function createUrl($host, $path, $protocol = 'http', $port = 80)
{
return "$protocol://$host:$port/$path";
}
createUrl(host: 'example.com', path: 'foo/bar', port: 8080);
// returns: "http://example.com:8080/foo/bar"

Get URL parameters inside custom module

I've created a custom block like this:
class HelloBlock extends BlockBase implements BlockPluginInterface{
/**
* {#inheritdoc}
*/
public function build() {
$config = $this->getConfiguration();
$result = db_query('SELECT * FROM {test}');
return array(
'#theme' => 'world',
'#test' => $result
);
}
}
And I now want to programmatically get some parameter from the URL.
For example:
If the URL is http://localhost/drup/hello/5569 I want to get hold of the value 5569 inside my module.
I have tried arg(1) and drupal_get_query_parameters() but I got this error messages:
Call to undefined function `Drupal\hello\Plugin\Block\arg()`
and
Call to undefined function `Drupal\hello\Plugin\Block\drupal_get_query_parameters()`
How can I get the parameters?
Use \Drupal\Core\Routing;:
$parameters = \Drupal::routeMatch()->getParameters();
The named parameters are available as
$value = \Drupal::routeMatch()->getParameter('slug_name_from_route');
Where 'slug_name_from_router' comes from your routing.yml path property
path: '/your/path/{slug_name_from_route}'
If you want the raw parameter without any upcasting you can get
$value = \Drupal::routeMatch()->getRawParameter('slug_name_from_route');
I used to get the parameter value from URL (localhost/check/myform?mob=89886665)
$param = \Drupal::request()->query->all();
And applied in my select Query
$query = \Drupal::database()->select('profile_register', 'p1');
$query->fields('p1');
$query->condition('p1.mobileno', $edituseprof);
$query->condition('publishstatus', 'no');
$result = $query->execute()->fetchAll();
But on multiple parameter value, i am now successful(Ex: http://10.163.14.41/multisite/check/myform?mob=89886665&id=1)
$query = \Drupal::database()->select('profile_register', 'p1');
$query->fields('p1');
$query->condition('p1.mobileno', $edituseprof['mob']);
$query->condition('p1.ids', $edituseprof['id']);
$query->condition('publishstatus', 'no');
$result = $query->execute()->fetchAll();
arg() is deprecated in drupal 8, however we can get values like arg() function does in drupal 7 & 6
$path = \Drupal::request()->getpathInfo();
$arg = explode('/',$path);
print_r($arg); exit();
The output would be parameters in url except basepath or (baseurl),
Array
(
[0] =>
[1] => node
[2] => add
)
To get query parameter form the url, you can us the following.
If you have the url for example,
domainname.com/page?uid=123&num=452
To get "uid" from the url, use..
$uid = \Drupal::request()->query->get('uid');
To get "num" from the url, use..
$num = \Drupal::request()->query->get('num');
$route_match = \Drupal::service('current_route_match');
$abc = $route_match->getParameter('node'); //node is refrence to what you have written in you routing file i.e:
in something.routing.yml
entity.node.somepath:
path: '/some/{node}/path'
I have used {node} as arg(1). And I can access it by using *->getParameter('node');
Hope this will work.
If your url is like this below example
http://localhost/drupal/node/6666
Then you have to get the full url path by
$current_path = \Drupal::request()->getPathInfo();
then explode the path to get the arguments array.
$path_args = explode('/', $current_path);
Another example if value passed by a key in url like below where id contains the value
http://localhost/drupal?id=123
You can get the id by given drupal request
$id = \Drupal::request()->query->get('id');
Here's the example of accessing URL parameters and passing them to a TWIG template,
I am considering you have already created your module and required files and suppose "/test?fn=admin" is your URL
In Your .module file implement hook_theme and define variables and template name (Make sure you replace "_" with "-" when creating the template file)
function my_module_theme () {
return [
'your_template_name' => [
'variables' => [
'first_name' => NULL,
],
];
}
Now create your controller and put below code in it.
namespace Drupal\my_module\Controller;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpFoundation\Request;
class MyModule extends ControllerBase {
public function content(Request $request) {
return [
'#theme' => 'my_template',
'#first_name' => $request->query->get('fn'), //This is because the parameters are in $_GET, if you are accessing from $_POST then use "request" instead "query"
];
}
}
Now in your TWIG file which should be "my-template.html.twig" you can access this parameter as,
<h3>First Name: {{ first_name }}</h3>
And its done.
Hope this helps.
The Drupal docs are great on this: https://www.drupal.org/docs/8/api/routing-system/parameters-in-routes
define your path variable in yaml
example.name:
path: '/example/{name}'
...
Add the variable in your method and use it
<?php
class ExampleController {
// ...
public function content($name) {
// Name is a string value.
// Do something with $name.
}
}
?>

Categories