PHP/Laravel
Hey, I'm moving into abstraction in php and am attempting to validate and store values based on whatever has been submitted, where I expect that the methods should neither know what to validate against and/or which class and method to use to do so -
What I've got works but I can see that there would be issues where classes/methods do not exist. Here lays my question.
If I were to call a method in the following format, which way would be best to 'check' if class_exists() or the method exists()?
public function store(Request $request)
{
$dataSet = $request->all();
$inputs = $this->findTemplate();
$errors = [];
$inputValidators = [];
foreach ($inputs as $input) {
$attributes = json_decode($input->attributes);
if (isset($attributes->validate)) {
$inputValidators[$input->name] = $input->name;
}
}
foreach ($dataSet as $dataKey => $data) {
if (array_key_exists($dataKey, $inputValidators)) {
$validate = "validate" . ucfirst($dataKey);
$validated = $this->caseValidator::{$validate}($data);
if ($validated == true) {
$inputValidators[$dataKey] = $data;
} else {
$errors[$dataKey] = $data;
}
} else {
$inputValidators[$dataKey] = $data;
}
}
if (empty($errors)) {
$this->mapCase($dataSet);
} else {
return redirect()->back()->with(['errors' => $errors]);
}
}
public function mapCase($dataSet)
{
foreach($dataSet as $dataKey => $data) {
$model = 'case' . ucfirst($dataKey);
$method = 'new' . ucfirst($dataKey);
$attribute = $this->{$model}::{$method}($dataKey);
if($attribute == false) {
return redirect()->back()->with(['issue' => 'error msg here']);
}
}
return redirect()->back->with(['success' => 'success msg here'])'
}
For some additional context, an input form will consist of a set of inputs, this can be changed at any time. Therefore I am storing all values as a json 'payload'.
When a user submits said form firstly the active template is found, which provides details on what should be validated $input->attributes, once this has been defined I am able to call functions from caseValidator model as $this->caseValidator::{$validate}($data);.
I do not think that any checks for existence will be needed here as the validation parameters are defined against an input, thus if none exist this check will be skipped using if (array_key_exists($dataKey, $inputValidators))
However, I am dispersing some data to other tables within the second block of code using mapCase(). This is literally iterating over all array keys regardless of if a method for it exists and thus the initial check cannot be made as seen in the first block. I've attempted to make use of class_exists() and method_exists but logically it does not fit and I cannot expect them to work as I'd like, perhaps my approach in mapCase is not correct? I guess if I'm defining a class for each key I should instead use one class and have methods exist there, which would remove the need to check for the class existing. Please advise
Reference:
$attribute = $this->{$model}::{$method}($dataKey);
Solved the potential issue by using class_exists(), considering I know the method names as they are the same as the $dataKey.
public function mapCase($dataSet)
{
foreach($dataSet as $dataKey => $data) {
$model = 'case' . ucfirst($dataKey);
if (class_exists("App\Models\CaseRepository\\" . $model)) {
$method = 'new' . ucfirst($dataKey);
$attribute = $this->{$model}::{$method}($dataKey);
}
if($attribute == false) {
return redirect()->back()->with(['issue' => 'error msg here']);
}
}
return redirect()->back->with(['success' => 'success msg here'])'
}
Related
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;
}
I'm wondering what the best approach is to control which model attributes a given user is allowed to view.
To control which attributes they are allowed to modify I'm of course using scenarios, but sometimes they should be allowed to view attributes which they are not allowed to modify, so I can't just use the same list of attributes.
I want to control it at a central point, so preferably within the model I would guess.
What is the best way, or Yii intended method, to approach this?
I was thinking that I needed something similar to scenarios so, building on that idea I have now tried to make a solution where I create a method called viewable on my model, which returns a list of attributes that should be visible for the current scenario of the model. For example:
public function viewable() {
$scenario = $this->getScenario();
if ($scenario == self::SCENARIO_DEFAULT) {
return [];
} elseif ($scenario == self::SCENARIO_EV_ADMIN) {
return $this->attributes(); //admin is allowed to see all attributes on the model
} elseif ($scenario == self::SCENARIO_EV_ORGANIZER_INSERT || $scenario == self::SCENARIO_EV_ORGANIZER_UPDATE) {
$attributes = $this->activeAttributes(); //use list of attributes they can edit as the basis for attributes they can view
array_push($attributes, 'ev_approved', 'ev_status'); //add a few more they are allowed to view
return $attributes;
} else {
return [];
}
}
Then eg. in GridView or DetailView I pass the list of columns/attributes through a helper that will filter out any attributes that were not returned by viewable. Eg.:
'attributes' => MyHelper::filterAttributes([
'eventID',
[
'attribute' => 'organizerID',
'value' => \app\models\Organizer::findOne($model->organizerID)['org_name'],
],
'ev_name',
....
], $model->viewable()),
My helper method being like this:
public static function filterAttributes($all_attributes, $attributes_to_keep) {
$output = [];
foreach ($all_attributes as $value) {
if (is_string($value)) {
$colon = strpos($value, ':');
if ($colon === false) {
$name = $value;
} else {
$name = substr($value, 0, $colon);
}
} elseif (is_array($value)) {
if ($value['attribute']) {
$name = $value['attribute'];
} elseif ($value['class']) {
// always leave special entries intact (eg. class=yii\grid\ActionColumn)
$output[] = $value;
continue;
} else {
new UserException('Attributes name not found when filtering attributes.');
}
} else {
new UserException('Invalid value for filtering attributes.');
}
if (in_array($name, $attributes_to_keep)) {
$output[] = $value;
}
}
return $output;
}
And in create.php/update.php (or _form.php actually) I do this:
$editAttribs = $model->activeAttributes();
$viewAttribs = $model->viewable();
....
if (in_array('organizerID', $viewAttribs)) {
echo $form->field($model, 'organizerID')->textInput(['disabled' => !in_array('organizerID', $editAttribs) ]);
}
....
Feedback is welcome!
I have one PHP class as below (part of the code):
class myclass{
private static $arrX = array();
private function is_val_exists($needle, $haystack) {
if(in_array($needle, $haystack)) {
return true;
}
foreach($haystack as $element) {
if(is_array($element) && $this->is_val_exists($needle, $element))
return true;
}
return false;
}
//the $anInput is a string e.g. Michael,18
public function doProcess($anInput){
$det = explode(",", $anInput);
if( $this->is_val_exists( $det[0], $this->returnProcess() ) ){
//update age of Michael
}
else{
array_push(self::$arrX, array(
'name' => $det[0],
'age' => $det[1]
));
}
}
public function returnProcess(){
return self::$arrX;
}
}
The calling code in index.php
$msg = 'Michael,18';
myclass::getHandle()->doProcess($msg);
In my webpage says index.php, it calls function doProcess() over and over again. When the function is called, string is passed and stored in an array. In the next call, if let's say same name is passed again, I want to update his age. My problem is I don't know how to check if the array $arrX contains the name. From my own finding, the array seems to be re-initiated (back to zero element) when the code is called. My code never does the update and always go to the array_push part. Hope somebody can give some thoughts on this. Thank you.
There is a ) missing in your else condition of your doProcess() function, it should read:
else{
array_push(self::$arrX, array(
'name' => $det[0],
'age' => $det[1]
)); // <-- there was the missing )
}
Here is a complete running solution based on your code:
<?php
class myclass{
private static $arrX = array();
private function is_val_exists($needle, $haystack) {
if(in_array($needle, $haystack)) {
return true;
}
foreach($haystack as $element) {
if(is_array($element) && $this->is_val_exists($needle, $element))
return true;
}
return false;
}
//the $anInput is a string e.g. Michael,18
public function doProcess($anInput){
$det = explode(",", $anInput);
if( $this->is_val_exists( $det[0], $this->returnProcess() ) ){
//update age of Michael
for ($i=0; $i<count(self::$arrX); $i++) {
if (is_array(self::$arrX[$i]) && self::$arrX[$i]['name'] == $det[0]) {
self::$arrX[$i]['age'] = $det[1];
break;
}
}
} else{
array_push(self::$arrX, array(
'name' => $det[0],
'age' => $det[1]
));
}
}
public function returnProcess(){
return self::$arrX;
}
}
$mc = new myclass();
$mc->doProcess('Michael,18');
$mc->doProcess('John,23');
$mc->doProcess('Michael,19');
$mc->doProcess('John,25');
print_r($mc->returnProcess());
?>
You can test it here: PHP Runnable
As I said in comments, it looks like you want to maintain state between requests. You can't use pure PHP to do that, you should use an external storage solution instead. If it's available, try Redis, it has what you need and is quite simple to use. Or, if you're familiar with SQL, you could go with MySQL for example.
On a side note, you should read more about how PHP arrays work.
Instead of array_push, you could have just used self::$arrX[] = ...
Instead of that, you could have used an associative array, e.g. self::$arrX[$det[0]] = $det[1];, that would make lookup much easier (array_key_exists etc.)
Can you try updating the is_val_exists as follows:
private function is_val_exists($needle, $haystack) {
foreach($haystack as $element) {
if ($element['name'] == $needle) {
return true;
}
return false;
}
I am having trouble using a variable generated in one function, as a variable in a second function.
The Problem:
I get Notice: Undefined variable: parameter in the validate function, on the line:
$this->$methodName($item,$value,$parameter) OR $valid=false;
When the function call for splitRulesAndParameters is simply replaced with the code within the function, the problem goes away.
The Scenario:
The following two functions are both within the Validator class, the first, validate, makes use of the second, splitRulesAndParameters
Here is the validate function:
public function validate($data, $rules)
{
$valid= true;
foreach($rules as $item=>$ruleSet)
{
$ruleSetArray=explode('|',$ruleSet);
foreach($ruleSetArray as $rule)
{
$this->splitRulesAndParameters($rule);
$methodName='validate'.ucfirst($rule);
$value = isset($data[$item]) ? $data[$item] : NULL;
if(method_exists($this, $methodName))
{
$this->$methodName($item,$value,$parameter) OR $valid=false;
}
}
}
return $valid;
}
And here is the splitRulesAndParameters function
public function splitRulesAndParameters($rule)
{
$position = strpos($rule, ':');
if($position !==false)
{
$parameter = substr($rule,$position + 1);
$rule = substr($rule,0,$position);
}
else
{
$parameter='';
}
}
Seeing as the problem goes away if you "inline" the code in splitRulesAndParameters, I suspect the $parameters variable is used in that method. If so, simply have that method return the value of this variable, and assign it to a variable local to the validate method you've posted here:
$parameters = $this->splitRulesAndParameters($rule);
After adding this to the splitRulsAndParameters method:
return $parameters;
The method itself also modifies the $rule value. Again: this $rule variable is local to each method. It may have the same name, but the value is a copy. Any changes you make to $rule in splitRulesAndParameters is not reflected by $rule in your validate method. If I were you, I'd write:
public function splitRulesAndParameters($rule)
{
$position = strpos($rule, ':');
if($position !==false)
{
return array(
'parameter' => substr($rule, $position+1),
'rule' => substr($rule, 0, $position)
);
}
return array(
'parameter' => null,//no param == null, IMO, change to '' if you want
'rule' => $rule
);
}
Then, to change the variables in validate:
$split = $this->splitRulesAndParameters($rule);
$rule = $split['rule'];
$parameter = $split['parameter'];
That ought to do it.
Side-note:
You seem to be validating everything that needs validating, even if the first validation failed. If I were you, I'd change this fugly statement:
$this->$methodName($item,$value,$parameter) OR $valid=false;
To a more efficient:
if (!$this->{$methodName}($item, $value, $parameter))
return false;//if validation fails, return false
That stops any further valiation from being executed: if one value is invalid, then just stop there. To continue is pointless, because the data-set is not entirely valid anyway.
Bonus:
Using a colon to separate the method name, and some parameter(s) does allow you to specify multiple params, too, and it allows you to simplify the splitRulesAndParameters some more:
protected function splitRulesAndParameters($rule)
{
$all = explode(':', $rule);
return array(
'rule' => array_shift($all),//removes first element in array
'params' => $all//rest of the array
);
}
Tweak this a little to better suite your needs
You can't just use a variable from inside a function in another function. You have to return the variable $parameter. Add a return statement to the end of splitRulesAndParameters and store the result in a variable inside validate ($parameter = $this->spli...).
You actually have two problems here, because you change the &rule variable inside your function, but you are passing it by reference. So after the fucntion is done the $rule variable is the same as it was before.
The way to solve this in the manner you are doing it right now would be to change the function to:
public function splitRulesAndParameters(&$rule)
{
$position = strpos($rule, ':');
if($position !==false)
{
$parameter = substr($rule,$position + 1);
$rule = substr($rule,0,$position);
}
else
{
$parameter='';
}
return $parameter;
}
and change the line
$this->splitRulesAndParameters($rule);
to
$parameter = $this->splitRulesAndParameters($rule);
I am trying to create an form validator class that will check to make sure that people have typed a particular number of characters into specified fields. Here is my page:
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
require_once('functions.php');
require_once('formValidator.php');
$validator = new FormValidator();
$validator->setFirst($_POST['first']);
$validator->setLast($_POST['last']);
$validator->setEmail($_POST['email']);
$errors = array();
$validator->checkLength($errors);
echo $errors;
if (!empty($errors)) {
echo '<p>' . $message . '</p>';
}
}
// form here (code above is in PHP tags, of course)
Here is my class:
class FormValidator {
public $first;
public $last;
public $email;
public $fields_with_lengths;
public function setFirst($first) {
$this->first = $first;
}
public function setLast($last) {
$this->last = $last;
}
public function setEmail($email) {
$this->email = $email;
}
function setLengths() {
$this->fields_with_lengths = array('first' => 2, 'last' => 2, 'email' => 8);
}
function checkLength($fields_with_lengths) {
$length_errors = array();
foreach($fields_with_lengths as $fieldname => $maxlength) {
if (strlen(trim($_POST[$fieldname])) > $maxlength) {
$length_errors[] = $fieldname;
}
}
if (!empty($length_errors)) {
$message = 'There were no errors';
} else {
$message = 'There were errors';
}
return $message;
}
}
$validator = new FormValidator();
$validator->setFirst($_POST['first']);
$validator->setLast($_POST['last']);
$validator->setEmail($_POST['email']);
Can anyone see what I am doing wrong? I'd be grateful for some help. Thanks.
You're passing an empty array into checkLength(), then iterating over it (so the for loop will never have anything to iterate over). You're returning $message from checkLength() but not assigning it to anything.
$errors = array('first' => 8,
'last' => 8,
'email' => 16
);
$message = $validator->checkLength($errors);
In PHP by default all values to functions are passed as values, not references (unless they are objects).
First of all, if you want to use a variable as a return value, you'd have to define checkLength($var) as:
function checkLength(&$fields_with_lengths) { /* ... */ }
You could in PHP4 do a call like checkLength(&$errors), but that's deprecated as of PHP5 and will throw an E_STRICT warning. Of course, you still can (but that functionality may be thrown away soon), though the "correct" way is, as said, giving it as reference.
An even more elegant way of acting, and what should really be done in PHP, is to return the $fields_with_lengths. This way:
function checkLength() {
$var = array();
// Your code
return $var;
}
And in the call to the function, just say
$return = $validator->checkLength();
But that does not solve the logic of your problem.
You are iterating over a parameter which is an empty array.
You are not iterating over $validator->fields_with_lengths.
Even more, $fields_with_lengths, in your code is not initialized ever.
You would have to call $validator->setLengths().
A better way of doing is (you are using PHP5) declaring a constructor, which in PHP5 is done with the magic function __construct().
However, as you are just initializing the variable always with the same constant values, you can do that in the very definition, just like this:
So you would have something like:
class FormValidator {
pubilc $first;
pubilc $last;
pubilc $email;
pubilc $fields_with_lengths = array('first' => 2, 'last' => 2, 'email' => 8);
// Rest of code
}
What's more..
¿Why are the attributes marked as public?
OOP theory tells us to close the scope of visibility as much as possible. And as you have setters, you don't need to access the attributes directly.
You can in PHP have a magic__get($var)to return a private $attribute if you want the syntactic sugar ofecho $validator->first;`.
Example:
function __get($var) {
if(isset($this->{$var})) return $this->{$var}; // this $this->{$var} could even be written as $this->$var but I did it like that for clarity.
}
If you also want the syntactic sugar of $validator->first = "John"; you can do the same with the setter, as in:
function __set($var, $value) {
if(isset($this->{$var})) {
return $this->{$var} = $value;
} return null; // Or you can throw an exception if you like
}
In PHP, as opposed to other languages, such as Java, you must explicitly use $this as the current instance of an object within a function.
So your checkLength is misdefined.
You should also do something with the return value, instead of just dropping it.
With just that minor modifications, you could fix your code with
function checkLength() {
$length_errors = array();
foreach($this->fields_with_lengths as $fieldname => $maxlength) {
if (strlen(trim($_POST[$fieldname])) > $maxlength) {
$length_errors[] = $fieldname;
}
}
if (!empty($length_errors)) {
$message = 'There were no errors';
} else {
$message = 'There were errors';
}
return array($length_errors, $message);
}
You were using a variable ($length_errors otherwise inaccessible! so you were computing data which would never be useful).
And in the call to the function, something like
$validator = new FormValidator();
// You could leave the setXXX($_POST[xxx])
$validator->first = $_POST['first'];
$validator->last = $_POST['last'];
$validator->email = ($_POST['email'];
$errors = $validator->checkLength();
echo "<p> {$errors[1]} </p>";
if(!empty($errors[0])) {
// You could do something like foreach($errors[0] as $k => $fieldname) { echo "Error in $fieldname<br />" }
}
$errors is empty because you initialize it but never change it. $message is unset because you never assign anything to it.
You're defining $errors = array();, making the errors an empty array. Nowhere do you attempt to set it to any other value, thus it remains an empty array.