PHPActiveRecord validates_uniqueness_of not working - php

I am currently getting the following error in my User model.
Slim Application Error
The application could not run because of the following error:
Details
Type: ActiveRecord\UndefinedPropertyException
Message: Undefined property: User->Array in /var/www/public_html/devsite/vendor/php-activerecord/php-activerecord/lib/Model.php on line 521
File: /var/www/public_html/devsite/vendor/php-activerecord/php-activerecord/lib/Model.php
Line: 521
My model only crashes the program when I add the line below to it.
static $validates_uniqueness_of = array(
'username'
);
If I remove the line above then the program runs again just fine. So I know it has something to do with this.
According to the documentation this should indeed be the format.
(http://www.phpactiverecord.org/projects/main/wiki/Validations#validates_uniqueness_of)
Reference to the validate function from the library is below:
Line 563 --
https://github.com/jpfuentes2/php-activerecord/blob/master/lib/Validations.php
I'm using PHP Version 7.0.15-0 on ubuntu0.16.04.4
If this is truly a bug, does anyone have any workarounds?

Ok I created a complete solution now. Anyone who can improve this is welcome.
First create a separate file and call it uniquecheck.php.
Inside it put this trait code.
trait uniquecheck {
//#JA - This function can do single and multi-unique checks.
//#JA - This is programmed to be replaced at a later date when validates_uniqueness_of is fixed (http://www.phpactiverecord.org/projects/main/wiki/Validations#validates_uniqueness_of)
//#JA - EXAMPLES
//SINGLE -- array('name','message' => 'Can't do this')
//MULTIPLE -- array( array('name1','name2'), 'message' => 'can't do this and that together')
//#JA - To be clear multiple does not mean 2 different uniques but a unique on 2 columns. Just use this function twice for 2 separate unique checks.
//#JA - Refer to (https://github.com/jpfuentes2/php-activerecord/issues/336)
public function uniquecheck($rules = array()) {
//#JA - If its an array use the MULTIPLE method
$dirty = $this->dirty_attributes();//#JA - Get list of attributes that have been modified since loading the model
if(is_array($rules[0])){
//#JA - Generate first part of condition string
$uniques = $rules[0];
foreach($uniques as $unique){
$conditionstring .= "$unique = ? AND ";
}
$conditionstring = substr($conditionstring, 0, -5);
$dirtyfound = false;
//#JA - Then generate the array we will use for the conditions
$conditionarray['conditions'][] = $conditionstring;
foreach($uniques as $unique){
$conditionarray['conditions'][] = $this->read_attribute($unique);
if(array_key_exists($unique, $dirty)){
$dirtyfound = true;
}
}
if ($dirtyfound == true) { //#JA - If one of the parts that makes the record unique is dirty then...
try {
//#JA - Whatever the primary key currently is return the object for that. This will be the object reference for what is not modified
$currently = Self::find($this->id);
}
catch (Exception $e) {
$currently = false;
}
foreach($uniques as $unique){
if ((
(is_object($currently) && $currently->$unique != $this->$unique)
|| !is_object($currently)
) && static::exists($conditionarray))
$this->errors->add($unique, $rules['message']);
}
}
}else{ //#JA - Otherwise use the SINGLE method
$unique = $rules[0];
if (array_key_exists($unique, $dirty)) { //#JA - If the value we are checking to be unique has been modified...
try {
//#JA - Whatever the primary key currently is return the object for that. This will be the object reference for what is not modified
$currently = Self::find($this->id);
}
catch (Exception $e) {
$currently = false;
}
//#JA - The dirty attributes array simply contains fields that have been set by our code.
//#JA - Ergo if we have re-applied the same value to our model, it will be classed as dirty even though it has not changed
//#JA - If $currently was returned as an object type AND its original value does not equal the current dirty value of the property on the model
//#JA - OR If the object returned was not an object (meaning it does not currently exists in the database)...
//#JA - OR it could mean that the table is just empty for the first time... Thus
//#JA - AND if the dirty value of the unique was found to exist then a unique was found.
if ((
(is_object($currently) && $currently->$unique != $this->$unique)
|| !is_object($currently)
) && static::exists(array($unique => $this->$unique)))
$this->errors->add($unique, $rules['message']);
}
}
}
}
To use this in your model just use the statement 'use uniquecheck;' after including the php file with reference to the trait. For example...
require_once('traits/uniquecheck.php');//#JA - Helper to check if values are unique
class Client extends ActiveRecord\Model {
use uniquecheck;
public function validate() {
$this->uniquecheck(array(array('company_id','contactfirstname','contactlastname', 'contactphonenumber', 'contactaddress'),'message' => 'Can\'t have duplicate client.'));
}
}
The above shows an example of how to check a multiple unique. This will work for new records AND for editing records since the trait is smart to know which fields are dirty or not.
If you are not using a multi-unique it works just like this instead.
public function validate() {
$this->uniquecheck(array('username','message' => 'Username already in use'));
}
I copied the format that they use on PHPActiveRecords documentation so it should work exactly the same now.
Hope this helps someone else!

public static $validates_uniqueness_of = array(
'username'
)

shot in the dark:
static $validates_uniqueness_of = array(
array('username')
);

Related

With php finding out if variable is this exact class instance

I have run into trouble figuring out how to compare two variables which might contain the exact same class instance.
An abstract class (part of which is shown below) has a method fetch_mother() designed to identify the object that should contain it and return that or simply return itself because it is at the bottom of the stack. In theory, that stack should be no more than 5 deep.
Most instances represent things like categories.
With the get get_full_path() method:
Expected output was: [siteurl] /system/drafts/example-one/also-dev-notes/
Actual output was: [siteurl] /drafts/drafts/[snip]/drafts/drafts/example-one/also-dev-notes/
Which means that the sanity check kicks in and breaks a loop. It also means that I have not correctly tested for the returned object being the same as $this.
How can I confirm is $var===$this?
Code where the problem takes place:
<?php
namespace modules\content\classes;
use modules\core\interfaces as i;
use modules\core\classes as c;
abstract class content_object extends c\module_lib {
// vars
// ...
protected $mother;
protected $map
// ... code ...
public function get_object_map(){
return $this->map;
}
/**
* Get the stream holding this item
* #return \modules\content\classes\error|\modules\content\classes\content_object
*/
public function &fetch_mother(){
if(isset($this->mother) && is_object($this->mother)){
return $this->mother;
}
$mother = $this->module()->find_object_stream($this);
if(!($mother instanceof \modules\core\error) && is_object($mother) && $mother != $this){
$this->mother = $mother;
return $mother;
}else{
// I am my own mother ? \\
return $this;
}
}
protected function fetch_full_path_from_mother($path='',$sanity=10){
$map = $this->get_object_map();
$mother = $this->fetch_mother();
$path = $map . '/' . $path;
if($this==$mother || !is_object($mother) || $sanity<1){
return $path;
}
$sanity--;
return $mother->fetch_full_path_from_mother($path,$sanity);
}
public function get_full_path(){
$home = $this->get_core()->factory()->get_config('home');
return $home . $this->fetch_full_path_from_mother();
}
}
The answer here is non-obvious.
<?php
$foo = $this;
if($foo==$this){
echo 'It is';
}else{
echo 'It is not';
}
The output of the above would be It is.
That's because if the two objects are the same instance then the == comparison would be enough to determine this.
Likewise (as per the comments) spl_object_hash($mother)==spl_object_hash($this) is also true only if it is the same object. However, if another object with the same properties were created the above would be false because they are separate objects.
This question and answers deals with that exact same topic: spl_object_hash matches, objects not identical
The assumption in my question (which I did not see at first) is that the lookup function is acting as a factory and caching objects. The differential conclusion must be that a copy or second instance is being returned.
Thus, the problem must be with the fetch_mother() method.
(Further investigation did indeed show that this was the problem.)
Solutions include checking for matching properties (which in this case works as there are several unique fields pulled from the database) or comparing print_r output.
if(print_r($mother,true)==print_r($this,true)){
// code
}
That particular solution is ugly, inelegant and not very reliable.
A better solution would be to implement an object cache higher up the stack. (Which is what I will be proposing).
TL;DR: Objects with identical properties are still not the same.

Check if the value already exists in the database using Laravel

Description: I have a site. I just want to keep track of a suspicious request and possible barn them only if needed. I just started to implement that feature. I have all the records of IP Addresses, but I'm not sure how to increment their visit count each time - they visit.
Goal: To increment visit_count attribute each time user visit a site
In my visitors table, I have an ip attribute
I want to check for an existing first before, I perform the saving, and other logics, but I'm just a little stuck here.
How do I check if the value already exists in the database using Laravel ?
Any hints on this will be much appreciated !
I've tried
Model : Visitor.php
class Visitor extends Model {
protected $table = 'visitors';
//Validation Rules and Validator Function
public static function validator($input, $id = ''){
$rules = array(
'ip' =>'unique:visitors,ip,'.$id,
);
return Validator::make($input,$rules);
}
}
Controller : Visitor.php
// Check for existing
$validator = Visitor::validator($ip);
if ($validator->fails()) {
$ip = Visitor::where('ip', '=', $ip)->firstOrFail();
$id = $ip['attributes']['id']; //<------ Not sure if this is a proper way
if($ip){
$visitor = Visitor::findOrFail($id);
$visitor->visit_count = $visitor->visit_count + 1 ;
$visitor->save();
}
} else {
$visitor = new Visitor;
$visitor->ip = $ip;
$visitor->visit_count = $visitor->visit_count + 1 ;
$visitor->save();
}
Result
I keep getting
Argument 1 passed to Illuminate\Validation\Factory::make() must be of the type array, string given
I believe, it from this line here $validator = Visitor::validator($ip);
The error message kind of gives it away. The Validator expects the values and the rules to be two separate arrays, each with keys denoting the columns name that needs to be validated. You have that for the rules, but not for the values being checked. This will fix your error:
return Validator::make(['ip' => $input], $rules);

Will ActiveRecord:link() method fail or pass on on incorrect data?

When using ActiveRecord:link() method, am I forced to update relations in a "secure" way:
foreach ($postData['User']['lab'] as $labId) {
$lab = Lab::findOne($labId);
if ($lab instanceof \app\models\Lab) {
$model->link('lab', $lab);
}
}
Or can I do this, the "lazy" way:
foreach ($postData['User']['lab'] as $labId) {
$model->link('lab', Lab::findOne($labId));
}
Without caring for extra checkings?
Will link fail or pass on, if its feed with null (because call to Lab::findOne($labId) won't find given record on certain iteration)?
It will give error as far as i see from code.
With via in relation or without it calls methods from $model and if Lab::findOne($labId) is null - you'll get an error.
if ($relation->via !== null) {
if ($this->getIsNewRecord() || $model->getIsNewRecord()) {
......
else {
$p1 = $model->isPrimaryKey(array_keys($relation->link));
......
And as documentation says:
Note that this method requires that the primary key value is not null.

Is there a way to check what type of a value a method returns?

I am writing a method which can call any method from any class (this process is dynamic).
In my method, I need to find out what type is the returned value, based on the returned value type,I will proceed on to the next step.
For example:
<?php
function identifyReturnType($className, $methodName) {
$result = $className->$methodName();
//Here I need to find out the $result type
}
?>
I have many classes where methods return bool, string, int etc.
and there are a few methods which do not return anything, those methods set the values in object or the object has resource pointer :
<?php
function getCategories() {
$this->query("SELECT * FROM categories");
}
function getItems() {
$this->query("SELECT * FROM items");
$this->getValues();
}
?>
PHP gettype($var) method finds out what is the value type but for this, my method must return a value. I have cases (as I explained above) where method just sets the query object.
Please share your ideas.
Thank you so much.
This really depends on your implementation. Some follow architecture where every function will return data as array. Even for query returned data is returned in small chunks of array. That is completely on how you optimize or write your script. Say you are getting all contacts and if you have say 10,000 contacts in DB and you return all in an array, thats a bad idea. Rather use pagination and return in small numbers if you want the function to return data as array.
I have had this issue, where we have a big web application written in PHP/Mysql. Over the time we have thousands of functions across different classes. Now we have to develop a REST API which will have different functionality. The main problem was we do not have used different functions to return query object, some to return array, some to return Boolean and so on. The API should return data as JSON. Now we have to choice use the existing code for different functionality or re-write new code for the API. The 2nd choice is more expensive so we are left with first choice. But the problem as I mentioned is far from over the methods will return different type and do we need to really write more codes to check which function is called and if the say function "xyz()" is called and we know its returning query object then loop through it generate array and then json. No thats a bad idea and will take a lot of effort and its better to write seperate code then.
So we follow the following approach.
Our api call looks like
www.api.oursite.com/api/v1/Resource/Method?param=....
Now we catch the Resource and Method where resource is a Class name and Method is a method name for that Class.
so we know we have to call Resource->Method()
Now we have a class called ResourceMethodMap.class.php and it contains the array as
static $resource_method_map = array(
"Users"=>array(
"getUserInfo"=> // gets the user info
array(
"return"=>"array",
"accessToken"=>true
)
),
....
...
)
So the API request processing code does something like
public function call_method($resource = "",$method=""){
if($resource == "") $resource = $this->get_resource();
if($method == "") $method = $this->get_api_method();
if (class_exists($resource)) {
$resource_obj = new $resource();
// Parse the method params as array
$param_array = $this->parse_method_params($resource,$method);
if(false !== $param_array){
$result = call_user_func_array(array($resource_obj, $method), $param_array);
}else{
$result = $resource_obj->$method() ;
}
return $this->process_return_data($resource,$method,$result,$resource_obj);
}else{
$this->setMessage("Invalid Resource");
return false ;
}
}
Here the function process_return_data() will do the returned data conversion as
function process_return_data($resource,$method,$ret_val,$resource_obj = NULL){
if(array_key_exists("return",ResourceMethodMap::$resource_method_map[$resource][$method])){
$return_type = ResourceMethodMap::$resource_method_map[$resource][$method]["return"];
$return_array= array();
switch($return_type){
case 'boolean':
if(false === $ret_val){
return false ;
}else{
if(is_array($ret_val)){
return $ret_val ;
}elseif(true === $ret_val){
return $ret_val ;
}else{
$return_array[] = $ret_val ;
return $return_array ;
}
}
break;
case 'array':
return $ret_val ;
break;
}
.....
}
}
So Yes it completely on the developer how they want their data to be returned. The above example is just one real time scenario how we have implemented.
I have posted the complete code her http://codepad.org/MPY1gVed have look
If i understood your question right you can do this by passing in an argument as a reference.
Here's an example i made for you, if it is any help.
http://php.net/manual/en/language.references.pass.php
Another solution can be to return an array with both the return value and the type.
Do you real need a method to call other methods? You could just instantiate the class and call it manually
In adittion i would recommend checking like so:
if(is_callable($className, $methodName)){
$className->$methodName();
}

Custom is_unique_logical_key - validation or callback?

Can I please have a design suggestion for the following problem:
I am using Codeigniter/Grocery_CRUD.
My system is multi tenanted - different autonomous sites - within the same client. I have quite a few instances of tables that have unique logical keys. One such table structure is:
equip_items
id (pk)
equip_type_id (fk to equip_types)
site_id (fk to sites)
name
Where (equip_type_id, site_id, name) together are a unique key in my db.
The issues is that when using a grocery_CRUD form to add or edit a record that breaks this database rule - the add or edit fails (due to the constraints in the db) but I get no feedback.
I need a variation on the is_unique form_validation rule by which I can specify the field*s* that must be unique.
The issues:
How to specify the rule? set_rules() is for a given field and I have multiple fields that the rule will apply to. Does that mean I should abandon the Form_validation pattern? Or do I follow the 'matches' rule pattern and somehow point to the other fields?
Perhaps a callback function would be better but this would mean writing a custom function in each model where I have this problem at last count this is 9 tables. It seems far better to do this in one place (extending form_validation).
Am I missing something already in codeigniter or grocery_CRUD that has already solved this problem?
Any suggestion/advice you might have would be appreciated.
EDIT:
Actually it appears the solution Johnny provided does not quite hit the mark - it enforces each field in unique_fields() being independently unique - the same as setting is_unique() on each one. My problem is that in my scenario those fields are a composite unique key (but not the primary key). I don't know if it is significant but further to the original problem statement: 1) site_id is a 'hidden' field_type - I don't want my users concerned they are on a different site so I'm dealing with site_id behind the scenes. 2) Same deal with an equip_status_id attribute (not part of the unique key). And 3) I have set_relations() on all these foreign key attributes and grocery_CRUD kindly deals with nice drop downs for me.
EDIT 2
I have solved this using a callback.
UPDATE: This code is now part of grocery CRUD version >= 1.4 and you don't need to use an extension anymore. For more see the documentation for unique_fields
I will try to explain it as easy as I can:
1. First of all for those who have grocery CRUD lower or equal to 1.3.3 has to use this small change: https://github.com/scoumbourdis/grocery-crud/commit/96ddc991a6ae500ba62303a321be42d75fb82cb2
2. Second create a file named grocery_crud_extended.php at application/libraries
3. Copy the below code at your file application/libraries/grocery_crud_extended.php
<?php
class grocery_CRUD_extended extends grocery_CRUD
{
protected $_unique_fields = array();
public function unique_fields()
{
$args = func_get_args();
if(isset($args[0]) && is_array($args[0]))
{
$args = $args[0];
}
$this->_unique_fields = $args;
return $this;
}
protected function db_insert_validation()
{
$validation_result = (object)array('success'=>false);
$field_types = $this->get_field_types();
$unique_fields = $this->_unique_fields;
$add_fields = $this->get_add_fields();
if(!empty($unique_fields))
{
$form_validation = $this->form_validation();
foreach($add_fields as $add_field)
{
$field_name = $add_field->field_name;
if(in_array( $field_name, $unique_fields) )
{
$form_validation->set_rules( $field_name,
$field_types[$field_name]->display_as,
'is_unique['.$this->basic_db_table.'.'.$field_name.']');
}
}
if(!$form_validation->run())
{
$validation_result->error_message = $form_validation->error_string();
$validation_result->error_fields = $form_validation->_error_array;
return $validation_result;
}
}
return parent::db_insert_validation();
}
protected function db_update_validation()
{
$validation_result = (object)array('success'=>false);
$field_types = $this->get_field_types();
$unique_fields = $this->_unique_fields;
$add_fields = $this->get_add_fields();
if(!empty($unique_fields))
{
$form_validation = $this->form_validation();
$form_validation_check = false;
foreach($add_fields as $add_field)
{
$field_name = $add_field->field_name;
if(in_array( $field_name, $unique_fields) )
{
$state_info = $this->getStateInfo();
$primary_key = $this->get_primary_key();
$field_name_value = $_POST[$field_name];
$ci = &get_instance();
$previous_field_name_value =
$ci->db->where($primary_key,$state_info->primary_key)
->get($this->basic_db_table)->row()->$field_name;
if(!empty($previous_field_name_value) && $previous_field_name_value != $field_name_value) {
$form_validation->set_rules( $field_name,
$field_types[$field_name]->display_as,
'is_unique['.$this->basic_db_table.'.'.$field_name.']');
$form_validation_check = true;
}
}
}
if($form_validation_check && !$form_validation->run())
{
$validation_result->error_message = $form_validation->error_string();
$validation_result->error_fields = $form_validation->_error_array;
return $validation_result;
}
}
return parent::db_update_validation();
}
}
4. Now you will simply have to load the grocery_CRUD_extended like that:
$this->load->library('grocery_CRUD');
$this->load->library('grocery_CRUD_extended');
and then use the:
$crud = new grocery_CRUD_extended();
instead of:
$crud = new grocery_CRUD();
5. Now you can simply have the unique_fields that it works like this:
$crud->unique_fields('field_name1','field_name2','field_name3');
In your case:
$crud->unique_fields('equip_type_id','site_id');
Pretty easy right?
This is checking if the field is unique or not without actually change the core of grocery CRUD. You can simply use the grocery_CRUD_extended instead of grocery_CRUD and update grocery CRUD library as normal. As I am the author of the library I will try to include this to grocery CRUD version 1.4, so you will not have to use the grocery_CRUD_extended in the future.
I have done this using a callback:
$crud->set_rules('name','Name','callback_unique_equip_item_check['.$this->uri->segment(4).']');
function unique_equip_item_check($str, $edited_id)
{
$var = $this->Equip_Item_model->is_unique_except(
$edited_id,
$this->input->post('site_id'),
$this->input->post('equip_type_id'),
$this->input->post('name'));
if ($var == FALSE) {
$s = 'You already have an equipment item of this type with this name.';
$this->form_validation->set_message('unique_equip_item_check', $s);
return FALSE;
}
return TRUE;
}

Categories