Laravel: model id goes to true before update - php

I've got quite a bizarre situation. I've got a piece of code that never gave any issues in the past. Since last night it behaves like this.
Before updating a model the id of that model goes to true. The function below is from a controller and gets called with a POST request. The request gets validated and when the model has not been exported it gets exported to another system. If the export is successful, the model gets updated with the appropriate values. The id does not get set in any stage of this process.
I've added comments to the code to give you an idea where, what happens.
public function export(Request $request, VeniceService $service, Invoice $invoice)
{
$invoice = $invoice->load([
'user', 'customer', 'extension.referenceValues.definition', 'lines'
]);
$this->enforce([
new CheckCstNum($invoice->customer),
new CheckReferences($invoice->extension),
], 432);
if ($invoice->to_export) {
DB::beginTransaction();
try {
var_dump($invoice->id); // returns the id
$data = $service->export($invoice);
var_dump($invoice->id); // returns the true
$invoice->book_date = Carbon::now();
$invoice->doc_num = $data['doc_num'];
$invoice->sys_num = $data['sys_num'];
$invoice->tsm_num = $data['tsm_num'];
$invoice->to_export = false;
$invoice->is_ticked = false;
var_dump($invoice->id); // This returns true
var_dump($invoice); // All the values are correct, except the id, this is set to true
$invoice->save(); // With the id as true, this throws an exception. Duplicate entries for PRIMARY key id, '1'
DB::commit();
$service->attachPdf($invoice, Printer::print($invoice)->output());
} catch (VeniceException $e) {
DB::rollBack();
return $e->render($request);
} catch (\Exception $e) {
DB::rollBack();
return response()->json($e->getMessage(), 500);
}
}
return new InvoiceResource($invoice->refresh()); // returns the invoice, but the id is still true
}
$this->service->export() resolves to this function. Before this happens, the id is still the original id of the model.
public function export($model)
{
return $this->call($model, __FUNCTION__);
}
protected function call($model, $function)
{
$class = $this->getClassName($model);
$method = "{$function}{$class}";
return $this->$method($model);
}
public function exportInvoice($invoice)
{
var_dump($invoice->id); // Returns the id
$veniceInvoice = (new VeniceInvoiceResource($invoice))->toArray(request());
var_dump($invoice->id); // Returns true...
return $this->request('POST', 'venice/invoices/' . $this->bookSales, [
RequestOptions::JSON => $veniceInvoice
]);
}
$veniceInvoice = (new VeniceInvoice($invoice))->toArray(request()); After this line the id is set as true. This really does not make any sense as it has always worked, and the model does not get manipulated in any way.
One last bit of code. But I do not think this has anything to do with the issue.
VeniceInvoiceResource.php
public function toArray($request)
{
$pdf = Printer::print($this->resource)->output();
$lines = $this->customer->standard_base == 10 ? VeniceInvoiceLineIC::collection($this->lines) : VeniceInvoiceLine::collection($this->lines);
$refs = $this->extension->referenceValues->map(function ($item) {
return [
'index' => 0,
'quantity' => 0,
'unit_price' => 0,
'description' => $item->definition->name . ' ' . $item->value,
'vat_code' => 0,
'ic_code' => 0,
];
})->toArray();
$details = array_merge($refs, $lines->toArray($request));
return [
'cst_num' => $this->customer->cst_num,
'book' => ($this->book === 'VKPCN') ? $this->book : config('venice.config.sales.book'),
'amount' => $this->total,
'vat_amount' => $this->total,
'exp_date' => carbon(config('venice.config.sales.date'))->addDays($this->customer->exp_term)->format('d/m/Y'),
'doc_date' => carbon(config('venice.config.sales.date'))->format('d/m/Y'),
'vat_system' => $this->customer->vat_system,
'bf_code' => $this->customer->bf_code,
'doc_type' => ($this->doc_type === 'slsCreditnote') ? 1 : 0,
'pdf' => base64_encode($pdf),
'pdfName' => $this->date->format('Ym') . '-' . $this->id . '.pdf',
'remark' => 'Clockwork ' . $this->date->format('Y F') . ' ' . $this->user->name,
'details' => $details,
];
}
For now I've added a temporary fix to mitigate the issue. I've created a clone of the $invoice. later I set the id of the original invoice to the cloned invoice id.
...
$invoice_copy = clone $invoice;
if ($invoice->to_export) {
DB::beginTransaction();
try {
$data = $service->export($invoice);
$invoice->book_date = Carbon::now();
$invoice->doc_num = $data['doc_num'];
$invoice->sys_num = $data['sys_num'];
$invoice->tsm_num = $data['tsm_num'];
$invoice->to_export = false;
$invoice->is_ticked = false;
$invoice->id = $invoice_copy->id;
$invoice->save();
DB::commit();
...
After a lot of debugging I have pinpointed where the id is set to true. I still don't know why.
In VeniceInvoiceResource $this->id before the PDF generation, the id is still the original invoice id. After the Printer, the id istrue.
If I look at the contructor for the resources, found in Illuminat\Http\Resources\JsonResource (Resource extends JsonResource) I see that $this->resource is set to the incomming value, in this case the $invoice.
/**
* Create a new resource instance.
*
* #param mixed $resource
* #return void
*/
public function __construct($resource)
{
$this->resource = $resource;
}
While in VeniceInvoiceResource $this->resource gets passed to the Printer instance. In the resource $this also has the values of the invoice.
/**
* Load items to print.
*
* #param $items
* #return $this
* #throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
public function print($items, $toPrint = null)
{
$items = is_array($items) || $items instanceof Collection ? $items : func_get_args();
foreach ($items as $item) {
if ($item instanceof Printable) {
foreach ($item->printData($toPrint) as $key => $data) {
switch($key) {
case 'merge':
$this->mergeOutput($data);
break;
case 'mergeFile':
$this->mergeFile($data);
break;
default:
$this->toPrint[] = $this->view->make($key, $data)->render();
}
}
} elseif ($item instanceof Renderable) {
$this->toPrint[] = $item->render();
} elseif (is_string($item)) {
$this->toPrint[] = $item;
} else {
throw new \InvalidArgumentException('Invalid argument');
}
}
return $this;
}
In the print method, $this->toPrint[] = $this->view->make($key, $data)->render(); gets used in this case. The output method looks like this.
/**
* Get the output as string.
*
* #return string
* #throws \iio\libmergepdf\Exception
*/
public function output()
{
return $this->generate();
}
/**
* Generate and merge.
*
* #return string
* #throws \iio\libmergepdf\Exception
*/
protected function generate()
{
$data = !empty($this->toPrint) ? $this->toPrint : [''];
$result = $this->pdf->getOutputFromHtml($data);
if (!empty($this->toMerge)) {
$this->merger->addRaw($result);
foreach ($this->toMerge as $item) {
$this->merger->addRaw($item);
}
$result = $this->merger->merge();
}
$this->toPrint = null;
$this->toMerge = null;
return $result;
}
In the print service nothing gets manipulated, it simply prints collections and items to a PDF format.
The last edit, because I found the line that caused all this. But I don't fully understand why it sets the id to true.
In Printer::print there is a call to a method on the model, printData() this method has an if statement to solve a problem we had with two invoices that needed some special treatment. There was not much time so we decided a simple if statement was sufficient enough for this situation.
if ($this->id = 4128 || $this->id === 4217) {
$vat_amount = 0;
$vat_label = '';
}
if you look closely you see that the first condition is not a condition... There is the problem, and the fix was simple. Remove this if statement as we don't need it any more. The invoices 4128 & 4217 already got printed and are archived. They do not need to be processed anymore.

Looks like you found the issue in your printData() method.
For why id ends up as true, it's due to the differing operator precedences.
The comparison operators (===) have a higher precedence than the logical operator (||), so the comparisons are done before the logical comparison. So, if the comparison operator had been correct, this is what would have been run (parens added for clarity):
($this->id === 4128) || ($this->id === 4217)
However, because the first operator was actually an assignment instead of a comparison, this changed the order of operation. The comparion and logical operators have a higher precedence than the assignment operator, so they are executed first. This is what was actually run (parens added for clarity):
$this->id = (4128 || $this->id === 4217)
So, id got assigned to the result of the logical comparison. Since all non-zero numbers evaluate to true, the logical comparison evaluated to true, and therefore id got set to true.

Related

How dou you write/call 2 tables in $this->db->table('table_name') in Codeigniter 4

Hello i have 2 tables that i want to call right now, for the EDIT (part of the CRUD)
tables:
table_a
table_b
i found in youtube how to update/edit from 2 tables, i need to call bot of the tables.
here's the code for the model
public function edit_this($ID_A)
{
return $this->db->table('table_a', '*i don't know how to insert the 2nd table')->where('ID_A', $ID_A)->get()->getRowArray();
}
Here's the controller
public function this_edit($ID_A)
{
$data = [
'title' => 'Admin',
'navbartitel' => 'You know this',
'alledit' => $this->theModel->edit_this($ID_A),
'validation' => \Config\Services::validation()
];
return view('this/all/edit', $data);
}
it works but i only can accsess the tabel_a, but i need them both so i can show what i've written in the edit form, from the database
anyone can help? thank you
$this->db->table(...) returns an instance of QueryBuilder and will happily accept a single string of comma-separated tables ("table1, table2..."), or even an array for that matter (['table1', 'table2'...]), as its first parameter. You are doing neither and instead passing multiple parameters.
When you call table(), the value passed in the first parameter is used during the creation of the database-specific Builder class:
public function table($tableName)
{
if (empty($tableName))
{
throw new DatabaseException('You must set the database table to be used with your query.');
}
$className = str_replace('Connection', 'Builder', get_class($this));
return new $className($tableName, $this);
}
The DB-specific Builder class has no constructor of its own so falls back on the __construct defined in BaseBuilder, which it extends:
public function __construct($tableName, ConnectionInterface &$db, array $options = null)
{
if (empty($tableName))
{
throw new DatabaseException('A table must be specified when creating a new Query Builder.');
}
$this->db = $db;
$this->from($tableName);
...
I've truncated this for brevity because the important part is that call to $this->from, which is in the end how multiple tables get processed:
public function from($from, bool $overwrite = false)
{
if ($overwrite === true)
{
$this->QBFrom = [];
$this->db->setAliasedTables([]);
}
foreach ((array) $from as $val)
{
if (strpos($val, ',') !== false)
{
foreach (explode(',', $val) as $v)
{
$v = trim($v);
$this->trackAliases($v);
$this->QBFrom[] = $v = $this->db->protectIdentifiers($v, true, null, false);
}
}
else
{
$val = trim($val);
// Extract any aliases that might exist. We use this information
// in the protectIdentifiers to know whether to add a table prefix
$this->trackAliases($val);
$this->QBFrom[] = $this->db->protectIdentifiers($val, true, null, false);
}
}
return $this;
}

Laravel 5: Why can't I use integers and floating point numbers in the language files?

Suppose I have this language file resources/lang/en/settings.php in a Laravel 5 project I'm working on. And this file looks like this:
<?php
return [
"key_1" => 50,
"key_2" => "50",
];
Now if I wanted to get the value of key_1 like this:
return trans("settings.key_1"); // returns "settings.key_1"
This will return settings.key_1 which is not 50, the value I expect. On the other hand, if I tried to get the value of key_2 which is also 50 but this time as a string, it will return 50 as expected.
return trans("settings.key_2"); // returns 50
So, Why can't I use numbers in the language files, Why the values must be strings?
From the sourcecode:
Lets start at the trans function that you are calling.
/**
* Get the translation for a given key.
*/
public function trans($id, array $parameters = [], $domain = 'messages', $locale = null)
{
return $this->get($id, $parameters, $locale);
}
The get function called by $this->get()
/**
* Get the translation for the given key.
*/
public function get($key, array $replace = [], $locale = null, $fallback = true)
{
list($namespace, $group, $item) = $this->parseKey($key);
// Here we will get the locale that should be used for the language line. If one
// was not passed, we will use the default locales which was given to us when
// the translator was instantiated. Then, we can load the lines and return.
$locales = $fallback ? $this->parseLocale($locale) : [$locale ?: $this->locale];
foreach ($locales as $locale) {
$this->load($namespace, $group, $locale);
$line = $this->getLine(
$namespace, $group, $locale, $item, $replace
);
if (! is_null($line)) {
break;
}
}
// If the line doesn't exist, we will return back the key which was requested as
// that will be quick to spot in the UI if language keys are wrong or missing
// from the application's language files. Otherwise we can return the line.
if (! isset($line)) {
return $key;
}
return $line;
}
As you can see here:
// If the line doesn't exist, we will return back the key which was requested as
// that will be quick to spot in the UI if language keys are wrong or missing
// from the application's language files. Otherwise we can return the line.
if (! isset($line)) {
return $key;
}
The value has not a valid value so isset is not passed therefore it will return the $key value which is the key you requested.
To go even further we can look at the following function which was called in the get function.
/**
* Retrieve a language line out the loaded array.
*/
protected function getLine($namespace, $group, $locale, $item, array $replace)
{
$line = Arr::get($this->loaded[$namespace][$group][$locale], $item);
if (is_string($line)) {
return $this->makeReplacements($line, $replace);
} elseif (is_array($line) && count($line) > 0) {
return $line;
}
}
Here we se the following:
if (is_string($line)) {
This is where the framework actualy checks if the value is a string.

Print name or definition of callable in PHP

Callables in PHP can be in a lot of forms, like an object, an array, or a string containing a function name.
If I got a callable like this in a variable how can I print some user friendly "definition" of it in the log.
Think of this code:
call_user_func($callable);
$logger->log("Provided callable " . (string) $callable . " called");
Problem is, this raises error, for example array to string conversion error. What is the best way to print out something useful about that callable?
Very old question but since i just used this to log some info, i thought it could use some clarification.
The comment by #Fabian Picone is a bit misleading.
The type hint actually works with string (arrays too), but the string MUST be an existing method or function (if you add function foo() {} in your code it will work). And that is, it must actually be callable. The error message is not that intuitive.
See this answer too https://stackoverflow.com/a/63289789/7409925.
This is my take that i'm using for logging (expanded from #Seb's), adding support for invokables and removing unnecessary trim's:
function getCallableName(callable $callable) {
switch (true) {
case is_string($callable) && strpos($callable, '::'):
return '[static] ' . $callable;
case is_string($callable):
return '[function] ' . $callable;
case is_array($callable) && is_object($callable[0]):
return '[method] ' . get_class($callable[0]) . '->' . $callable[1];
case is_array($callable):
return '[static] ' . $callable[0] . '::' . $callable[1];
case $callable instanceof Closure:
return '[closure]';
case is_object($callable):
return '[invokable] ' . get_class($callable);
default:
return '[unknown]';
}
}
Something like this should work:
function getCallableName($callable) {
if (is_string($callable)) {
return trim($callable);
} else if (is_array($callable)) {
if (is_object($callable[0])) {
return sprintf("%s::%s", get_class($callable[0]), trim($callable[1]));
} else {
return sprintf("%s::%s", trim($callable[0]), trim($callable[1]));
}
} else if ($callable instanceof Closure) {
return 'closure';
} else {
return 'unknown';
}
}
Inspired by an answer of #Bigdot https://stackoverflow.com/a/68113840/6916271 I created 2 methods, which could be useful when we need to retrieve the context of callable.
I used result together with Monolog, but it is also possible to use it with print_r(), json_encode(), var_dump() or var_export() if you need to convert it to string.
The main difference here compared to the answer above is extended information about closure, which might be needed during an investigation.
/**
* Retrieve the context of callable for debugging purposes
*
* #param callable $callable
* #return array
*/
private function getCallableContext(callable $callable): array
{
switch (true) {
case \is_string($callable) && \strpos($callable, '::'):
return ['static method' => $callable];
case \is_string($callable):
return ['function' => $callable];
case \is_array($callable) && \is_object($callable[0]):
return ['class' => \get_class($callable[0]), 'method' => $callable[1]];
case \is_array($callable):
return ['class' => $callable[0], 'static method' => $callable[1]];
case $callable instanceof \Closure:
try {
$reflectedFunction = new \ReflectionFunction($callable);
$closureClass = $reflectedFunction->getClosureScopeClass();
$closureThis = $reflectedFunction->getClosureThis();
} catch (\ReflectionException $e) {
return ['closure' => 'closure'];
}
return [
'closure this' => $closureThis ? \get_class($closureThis) : $reflectedFunction->name,
'closure scope' => $closureClass ? $closureClass->getName() : $reflectedFunction->name,
'static variables' => $this->formatVariablesArray($reflectedFunction->getStaticVariables()),
];
case \is_object($callable):
return ['invokable' => \get_class($callable)];
default:
return ['unknown' => 'unknown'];
}
}
/**
* Format variables array for debugging purposes in order to avoid huge objects dumping
*
* #param array $data
* #return array
*/
private function formatVariablesArray(array $data): array
{
foreach ($data as $key => $value) {
if (\is_object($value)) {
$data[$key] = \get_class($value);
} elseif (\is_array($value)) {
$data[$key] = $this->formatVariablesArray($value);
}
}
return $data;
}
In we use logger
try {
\call_user_func($callable);
} catch (\Throwable $e) {
$logger->log(
'Error occurred',
['exception' => $e, 'callable' => $this->getCallableContext($callable)]
);
//In case we can use string only
$logger->log('Error occurred: ' . \print_r($this->getCallableContext($callable), true));
}

Function returns string on first call, and an object on subsequent calls

I'm a little bit baffled by this, so I'm hoping someone can shed some light on it for me.
I have a function which is meant to return a column Status from one of my tables, visit.
function someFunction($visitId = null) {
$visit = VisitQuery::create()
->select(array('Status'))
->findPk($visitId);
}
If I var_dump($visit), when calling the function for the first time, it outputs:
string '1' (length=1)
Subsequent, identical calls to the function however seem to return an entire object:
object(Visit)[30]
protected 'startCopy' => boolean false
protected 'id' => int 362
protected 'job_id' => int 351
protected 'company_id' => int 2
protected 'type_id' => int 1
protected 'visit_date' => string '2013-08-23 00:00:00' (length=19)
protected 'status' => string '1' (length=1)
...
I'm calling the function for the first time with an (int) $visitId passed via a posted form:
var_dump($visitId); // int 362
Subsequent calls are made with an (int) $visitId which is returned from another function, saveVisit() (which uses Propel to save the record - I believe this may have something to do with it).
$visitId = saveVisit($visitId);
var_dump($visitId); // int 362
I tried to do some debugging, and for some reason the query issued to MySQL is different between the first function call and subsequent ones:
var_dump($con->getLastExecutedQuery());
SELECT visit.STATUS AS "Status" FROM `visit` WHERE visit.ID=362 // 1st call
SELECT `ID`, `JOB_ID`, `COMPANY_ID`, `TYPE_ID`, `VISIT_DATE`, `STATUS`, `REMIND`, `PRINTED` FROM `visit` WHERE `ID` = 362 // 2nd call
SELECT `ID`, `JOB_ID`, `COMPANY_ID`, `TYPE_ID`, `VISIT_DATE`, `STATUS`, `REMIND`, `PRINTED` FROM `visit` WHERE `ID` = 362 // 3rd call
Can anyone tell me why or how this is happening?
I'm using Propel 1.6.
Propel's create() method:
public static function create($modelAlias = null, $criteria = null)
{
if ($criteria instanceof VisitQuery) {
return $criteria;
}
$query = new VisitQuery();
if (null !== $modelAlias) {
$query->setModelAlias($modelAlias);
}
if ($criteria instanceof Criteria) {
$query->mergeWith($criteria);
}
return $query;
}
Propel's findPk() method:
public function findPk($key, $con = null)
{
if ($con === null) {
$con = Propel::getConnection($this->getDbName(), Propel::CONNECTION_READ);
}
// As the query uses a PK condition, no limit(1) is necessary.
$this->basePreSelect($con);
$criteria = $this->isKeepQuery() ? clone $this : $this;
$pkCols = $this->getTableMap()->getPrimaryKeyColumns();
if (count($pkCols) == 1) {
// simple primary key
$pkCol = $pkCols[0];
$criteria->add($pkCol->getFullyQualifiedName(), $key);
} else {
// composite primary key
foreach ($pkCols as $pkCol) {
$keyPart = array_shift($key);
$criteria->add($pkCol->getFullyQualifiedName(), $keyPart);
}
}
$stmt = $criteria->doSelect($con);
return $criteria->getFormatter()->init($criteria)->formatOne($stmt);
}
My function to retrieve the visit status:
function getVisitStatus($visitId = null) {
if (empty($visitId)) {
return false;
}
try {
$visit = VisitQuery::create()
->select(array('Status'))
->findPk($visitId);
} catch (Exception $e) {
echo $e->getMessage(); exit;
}
if (is_null($visit)) {
return false;
}
return $visit;
}
The function which saves a visit record:
function saveVisit($data = null) {
if (empty($data)) {
return false;
}
try {
$visit = (!empty($data['visit_id'])) ? VisitQuery::create()->findPk($data['visit_id']) : new Visit();
if (!is_object($visit)) {
return false;
}
$visitDataMap = array(
'JobId' => 'job_id',
'TypeId' => 'type_id',
'VisitDate' => 'visit_date',
'Status' => 'status',
'CompanyId' => 'engineer_id',
'Remind' => 'remind'
);
$visitData = array();
foreach ($visitDataMap as $column => $value) {
if (!empty($data[$value])) {
$visitData[$column] = $data[$value];
}
}
$visit->fromArray($visitData);
$visit->save();
return $visit->getId();
} catch (PropelException $e) {
echo $e->getMessage(); exit;
}
}
It seems that on the first call will grab your data but then put a copy of the full object in to the instance pool. I am not sure if this is a bug or valid behaviour (your code would suggest the former, but I'd love to hear from someone who knows more about Propel such as a dev) but you can stop it happening with:
VisitPeer::clearInstancePool();
Bear in mind you're losing a bit of caching from other queries you might have done here with the visit relation.
You will be able to confirm this by adding an echo in the BaseVisitPeer.php file. You will see something like this:
public static function getInstanceFromPool($key)
{
if (Propel::isInstancePoolingEnabled()) {
if (isset(VisitPeer::$instances[$key])) {
return VisitPeer::$instances[$key];
}
}
return null; // just to be explicit
}
If you add an echo somewhere inside the if (isset(VisitPeer::$instances[$key])) { statement, you should see it appear only after the second and subsequent calls. If you were to comment this whole if statement out then you would get the same result back each time - the one you correctly get back from your original call.
Hope that helps!

Check type of optional parameter in PHP

I am checking the type of optional parameters in PHP like this:
/**
* Get players in the team using limit and
* offset.
*
*
* #param TeamInterface $participant
* #param int $limit
* #param int $offset
* #throws \InvalidArgumentException
* #return Players of a team
*/
public function getPlayers(TeamInterface $team, $limit = null, $offset = null)
{
if (func_num_args() === 2 && !is_int($limit) ){
throw new \InvalidArgumentException(sprintf('"Limit" should be of int type, "%s" given respectively.', gettype($limit)));
}
if (func_num_args() === 3 && (!is_int($limit) || !is_int($offset))){
throw new \InvalidArgumentException(sprintf('"Limit" and "Offset" should be of int type, "%s" and "%s" given respectively.', gettype($limit), gettype($offset)));
}
//.....
}
This works but there are 2 main issues with this:
1/ If I need to check the type of 4/5 optional parameters for the same int type, the code become unnecessarily long. Any ideas how to make this piece of code more maintainable? (Maybe use only one if statement to check the same type of both $limit and $offset)
2/ getPlayers($team, 2, null) throws an exception. Is this ok knowing that the function can actually handle a null value here?
You could do a for loop with an array of args. Something like:
$args = func_get_args();
for ($i = 1; $i < 4; $i++) {
if ($args[$i] !== null and !is_int($args[$i])) {
throw ...
}
}
Of course, adjust the for conditions based on your number of arguments that need to be checked.
Or...
$args = func_get_args();
// skip first
array_shift($args);
foreach ($args as $arg) {
if ($arg !== null and !is_int($arg)) {
throw ...
}
}
For 1) I would check each variable individually and throw an exception for each:
if (!is_int($limit)){
//Throw
}
if (!is_int($offset))){
//Throw
}
This still requires an if statement for each variable but is a bit less verbose.
For 2) if null values are allowed you can change the check to be something like:
if ($offset && !is_int($offset))){
//Throw
}
Finally I wouldn't recommend checking func_num_args(). In your example code calling your function with too many arguments would bypass the validation.
PHP doesn't have type hints for scalars yet.
Redesign
When you start to take a lot of optional arguments in your function you develop code smells. Something is wrong, there is an object waiting to emerge.
Build all of your optional parameters as an Object and have a validate method on it.
I think you want a GameParameters object and have a validate method on it.
getPlayers($gameParameters) {
}
Move your validation of the parameters to that object where you can build it into each setter or have a comprehensive validate() function.
Combinatorial problem
As far as the explosion of checks goes I would build an array of errors and throw that if there are errors. This can be done with or without redesign.
if ($limit != null && !is_int($limit){
#add to the errors array
}
if ($offset != null && !is_int($offset){
#add to the errors array
}
if (errors) {
throw new \InvalidArgumentException(sprintf('"Limit" and "Offset" should be of int type, "%s" and "%s" given respectively.', gettype($limit), gettype($offset)));
}
Personally I prefer to have only one argument per function (unless the function is very simple), For example the function can take $request, and returns a tree of data $response. It makes it a bit easier to loop over and extend later:
function dostuff( $request ) {
$team = #$request['team'];
$limit = #$request['limit'];
$offset = #$request['offset'];
// ...
return $response;
}
Then for validation, you can write a set of rules at the top of the function like
// define validation rules
$rules = array( 'required' => array('team'),
'depends' => array('offset' => 'limit'),
'types' => array('offset' => 'int', 'limit' => 'int' ),
);
And centralize all your error checking in one call:
// can throw exception
argcheck( array( 'request' => $request, 'rules' => $rules ) );
This might need optimization, but the general approach helps contain bloat as you increase the complexity of the functions.
Use switch to code specific functions.
switch(gettype($limit)) {
case "integer":
//do other processing
break;
}
You cannot leave your code vulnerable like that. As for a safe solution to overcome the vulnerabilities. Create a safe list like this.
public function getPlayers(TeamInterface $team, $limit = null, $offset = null) {
$safelist = array("var1" => "TeamInterface", "var2" => "integer", "var3" => "integer");
$args = function_get_args();
$status = true;
foreach($args as $key => $var) {
if(gettype($var)!=$safelist["var".$key]) {
$status = false;
break;
}
}
if(!$status) break;
//...........
}

Categories