Setting the key of an object property that is an array - php

Just today I noticed a strange behavior in an object model that was previously working just fine (I have checked everything possible and nothing about its configuration has changed, so I am suspecting a change to PHP version and wondering if anyone else has experience anything similar)
Until recently, I could set the keys of object properties that were arrays manually. The specific implememation of this in one of my models was contained in a gallery class that looked like this:
public function __construct($gid){
parent::__construct($gid);
$this->Photos = $this->getPhotos();
$this->AlbumCover = $this->getCover();
}
public function getPhotos(){
$sql = 'SELECT GalleryPhotoID FROM GalleryPhoto WHERE GalleryID = ?';
$params = array($this->GalleryID);
$allids = DatabaseHandler::GetAll($sql, $params);
$output = array();
foreach($allids as $id){
$gp = new GalleryPhoto($id['GalleryPhotoID']);
$output[$gp->GalleryPhotoID] = $gp;
}
return $output;
}
Irrelevant parts omitted.
Basically, I could set the array keys of the Gallery's Photos object to the individual photo's id in the database. This just made it easier to code for individual iteration and made the whole thing run smoother.
Now, no matter what I set that key to, automatic integers are generated when the foreach runs. I even tried typing a literal string in there, which theoretically should replace every iteration, but I still got incremented, automatic integers for the keys of the property Photos.
[Photos] => Array
(
[0] => GalleryPhoto Object
(
[GalleryID] => 9
[Caption] =>
[Orientation] => 0
[AlbumCover] =>
[DateAdded] => 2011-01-03 16:58:51
[GalleryPhotoID] => 63
[Thumbnail] =>
[Image] =>
[src] => http://..com/galleryImage/getImage/63
)
[1] => GalleryPhoto Object
(
[GalleryID] => 9
[Caption] =>
[Orientation] => 0
[AlbumCover] =>
[DateAdded] => 2011-01-03 16:58:51
[GalleryPhotoID] => 64
[Thumbnail] =>
[Image] =>
[src] => http://..com/galleryImage/getImage/64
)
)
Has the abillity to manually set keys within an object property that is an array been removed in some minor release and I am unaware of it? I have googled all over, looked through the PHP manual site and found no answer. Has anyone experienced anything similar? Is there a better approach I should consider? I only really went with this because it made it so much easier to implement a next/previous system via ajax requests back to the next logical id (keeping in mind that ids can be deleted between!)
Thanks!

I don't see anything wrong with what you have, and I've never experienced the behavior you describe. However, a quick solution could be to replace the assignment line with something like this:
$output[$id['GalleryPhotoID']] = $gp;
You could also echo $gp->GalleryPhotoID; to ensure that the GalleryPhotoID property can actually be accessed that way.
Lastly, you said you replaced the above line with something akin to:
$output['foobar'] = $gp;
and it still created a new entry, with integer keys, for each entry? If that's the case, then I think there may be something in the code you omitted that's causing the problem.

Facepalm all the way. The effluvium of New Year's must still be in my brain, else I would have noticed that the function I added to fetch the album cover thumbnail shuffled the array if there wasn't a photo with the AlbumCover property set!
private function getCover(){
foreach($this->Photos as $ind=>$p){
if($p->AlbumCover){
return $this->Photos[$ind];
}
}
shuffle($this->Photos); //this is the problem
return current($this->Photos);
}
I amended this to just make a local copy of the variable and shuffle that instead if no cover is set.
private function getCover(){
foreach($this->Photos as $ind=>$p){
if($p->AlbumCover){
return $this->Photos[$ind];
}
}
$Photos = $this->Photos;
shuffle($Photos);
return current($Photos);
}
I accepted and upvoted both the answer and the comment posted since your caveats lead me to my error. Thanks guys!

Related

How to get only the first instance of an array that is true of a condition

I'm new to PHP and I'm trying to modify my Wordpress-based Learning Management theme (called Academy on ThemeForest) to be able to work out which lesson in the current course the user is up to.
In other words, I want to run a check to see which lessons the user has completed, getting only the ID of the first lesson in the course hierarchy that has not been completed.
Here's everything I know:
Within the loop of a single post (in this case a "course"), this is how I get the array of the current course's lessons:
<?php $lessons_array = ThemexCourse::sortLessons(ThemexCourse::$data['course']['lessons']); ?>
This produces this nested array:
Array ( [0] => WP_Post Object ([ID] => 117 [menu_order]=>1) [1] => WP_Post Object ([ID] => 124 [menu_order]=>2) [2] => WP_Post Object ([ID] => 156 [menu_order]=>3))
I've truncated it a bit since the two values, [ID] and [menu_order], are the most important: they tell you the ID of each lesson and their hierarchy in the course.
But this is where I get stuck: I don't want to get all of the lesson IDs, just the one the user has yet to complete.
In order to check if a user has completed a lesson or not, I've been using this:
<?php if(ThemexCourse::isCompletedLesson($lesson_ID)) { echo 'Completed'; } ?>
So using the above information, is it possible to return a single ID of only the next incomplete lesson?
Thanks to anyone in advance for your help!
I think that should do it:
$next_lesson = NULL;
foreach($lessons_array as $index=>$lesson) {
if(!ThemexCourse::isCompletedLesson($lesson->ID)) {
$next_lesson = $lesson;
break;
}
}
echo "Next lesson is: " . $next_lesson->ID;

how can I read object values and atributes with php?

Ive been working with some code and I am recieving a var (I didnt work the entire code, so, I dont know how it was made), my problem is that I get something like this
AdminUserRoleDecorator Object (
[user:AdminUserRoleDecorator:private] => EssUserRoleDecorator Object (
[user:EssUserRoleDecorator:private] => User Object (
[topMenuItemsArray:User:private] => Array ( )
[employeeList:User:private] => Array ( )
[activeProjectList:User:private] => Array ( )
[empNumber:User:private] => [allowedActions:User:private] => Array ( )
[nextState:User:private] => [userId:User:private] => 1
[userTimeZoneOffset:User:private] => -6
To be honest, and It could sound like a very stupid question, I dont know how to read that, normally I get the atributes in the way $myobject->atribute , now this I really have no idea, any way I can access to this? for example, I want to get the userId, I see its there, with :user:private (which I also dont know what are they for).
If I try
$myobject->User;
for example, I get nothing back.
Thanks.
EDIT:
I tried $myobject->user
and I am getting this
Fatal error: Cannot access private property AdminUserRoleDecorator
I am working with symfony by the way.
From the answer I gave here, you can get some insight into objects with get_class_methods() (php reference) and get_class_vars(). On that post, I made up a function to help me learn more about class methods available:
show_methods($playlistListFeed);
function show_methods( $_a ) {
echo "<h3>Methods for ".get_class($_a)."</h3>";
$_a= get_class_methods($_a);
$_a=array_unique($_a);
array_multisort(&$_a);
$i=0;
foreach( $_a as $method ) {
$i++;
printf("%-30.30s",$method);
if($i%5==0)
echo "\n";
}
}

Unable to access array member - UPDATED

I have a PHP function that returns an array. This is the output of print_r($myArray):
Array (
[id] => 8166
[customer_id] => 73
[nickname] => AnnieB
[name] => Anastasia Beaverhausen
[email] => annieb#annieb.com
[phone] => 555-555-5555
[company] => Annie B's
[address1] => 123 Main Street
[address2] => Apartment 555
[city] => Chicago
[state] => IL
[zip] => 55555
[billing] => 1
[residence] => 0
[token] =>
[verified] => 1 )
I should be able to access any of the members by saying something like $myArray['city'], correct? I know I've done this in the past, but it keeps returning an empty string, even when there is a value in the city field.
Any ideas?
==================MORE CODE POSTED PER REQUESTS=============================
I'm using this in Joomla, so there are a few lines that are specific to Joomla. The end purpose of this code is to populate a dropdown list with addresses from a database; the option values contain an imploded string of all column values (to be accessed via javascript later) and the option text is a single field. Here's the code that creates the dropdown:
foreach (getAddresses($AcctID) as $id => $info) {
print_r($info);
$nickName = $info[2];
error_log("nickname=".$nickName);
$infoStr = implode("|", $info);
$addressOptions .= "<option value=\"{$infoStr}\">$nickName</option>";
}
The getAddresses function is here (this is working correctly):
function getAddresses($AcctID) {
$db =& JFactory::getDBO();
$query = "select * from jos_customers_addresses where customer_id = ".$db->quote($AcctID);
$db->setQuery($query);
if (!$db->query()) error_log($db->stderr());
if (!$db->getNumRows())
return false;
else
return $db->loadAssocList();
}
The print_r($info) line is what is returning the array I originally posted. The next two lines are the ones giving me problems:
$nickName = $info[2];
error_log("nickname=".$nickName);
I've also tried $nickName = $info['nickname'] and gotten the same result - no value, even though there's obviously a value in the print_r, and the value does come through correctly in the code generated by the implode line. If you can find someplace between those two lines where I'm overwriting my variable, or whatever, please point it out to me, because I'm clueless as to why I can't get a handle on this value.
I should be able to access any of the
members by saying something like
$myArray['city'], correct?
Yes, that's correct.
I know I've done this in the past, but
it keeps returning an empty string,
even when there is a value in the city
field.
If nothing is showing up, there's probably not a value there, which as the comments said, is most likely a bug in your code (something overwriting that key). To be sure there's nothing there, instead of echo $myArray['city'] try var_dump($myArray['city']) -- this will tell you if it's an empty string (string(0) "") or not set at all (NULL)
Take your print_r($myArray) and put it immediately before the statement where you use $myArray['city']. is the 'city' key still set? If so, double check to make sure you don't have a typo... capitalization matters! If it's not still set, move the print_r($myArray) up a few lines and repeat until you find what's unsetting $myArray['city']
...Or just post more code ;-)
EDIT: Did $info[2] need to be $info['city']?

Drupal 6: Only inserting first character of value to MySQL

I am working with a hook_form_alter on a CCK type (for you drupal-ers). I have a field that is normally a select list in my node form. However, in this instance, I want to hide the select list, and populate its value in the form with an SQL query.
Everything was going nicely. I could see that my desired value was showing up in the HTML source, so I knew my query was executing properly. However, when I submit the form, it only inserts the first character of the value. A few of my tests were values of 566, 784, 1004 - the column values were 5,7,1, respectively.
At first I thought it had to be the DB column attributes, but when I removed my form_alter that makes the field hidden and select the value manually, the correct value is inserted?!?
<?php
function addSR_form_service_request_node_form_alter(&$form, $form_state) {
if (arg(0) == 'user' && is_numeric(arg(1))) {
$account = arg(1);
$club = 2589;
$form['field_sr_account'] = array( '#type' => 'hidden',
'#value' => $club
);
}
}
?>
Can anyone see why only the first character would be inserted??
Note: I have tried deleting and recreating the column, using #value & #default_value, and it is still submitting only the first character of the integer. Also, I eliminated the submit handler as a possible cause by removing it, which still resulted in only one character being submitted
More Updates - Still Searching!
Okay, some good questions. Allow me to answer them:
The DB column type is integer(4)
The HTML the hook produces is :
input type="hidden" name="field_sr_account" id="edit-field-sr-account" value="2589"
Latest Update: I think the issue has been narrowed to the structure of the array. When I do var_dump on this field after the form alter has been processed, this is what I get..
[43] => Array
(
[#type] => hidden
[#default_value] => 2589
[#post] => Array
(
)
[#programmed] =>
[#tree] =>
[#parents] => Array
(
[0] => field_sr_account
)
[#array_parents] => Array
(
[0] => field_sr_account
)
[#weight] => 0.016
[#processed] => 1
[#description] =>
[#attributes] => Array
(
)
[#required] =>
[#input] => 1
[#process] => Array
(
[0] => form_expand_ahah
)
[#name] => field_sr_account
[#id] => edit-field-sr-account
[#value] => 2589
[#defaults_loaded] => 1
[#sorted] => 1
)
What is the structure of the field that I can set the form value to. It's gotta be something like what abhaga is suggesting..
Since the field you are trying to change was originally using a select widget, CCK will be looking for $form_state['values']['field_sr_account'][0]['value']. By setting the field to a #hidden type and setting #value, you will get its value in $form_state['values']['field_sr_account']. CCK will try to access the first element of that and end up with the first character of the value.
Updated: The easiest way to achieve what you need would be to do something:
function addSR_form_service_request_node_form_alter(&$form, $form_state) {
if (arg(0) == 'user' && is_numeric(arg(1))) {
$account = arg(1);
$club = 2589;
// Use this property to store the value to restore back
$form['#field_sr_account'] = $club;
$form['field_sr_account'] = array( '#type' => 'hidden','#value' => $club);
}
}
/*in your submit handler, restore the value in the proper format*/
$form_state['values']['field_sr_account'] = array('0' => array('value' => $form['#field_sr_account']));
Old Answer
One way of accomplishing what you are
trying to do is to copy the whole
$form['field_sr_account'] into
$form['#field_sr_account'] and then
provide the value through the SQL
query in the right format in the
submit handler itself.
Ok take a look at http://api.drupal.org/api/drupal/developer--topics--forms_api_reference.html#hidden versus http://api.drupal.org/api/drupal/developer--topics--forms_api_reference.html#value
It is also recommended you use value instead of hidden. You can find this info on http://api.drupal.org/api/drupal/developer--topics--forms_api.html/6
Also, type hidden is not allowed to have properties your assigning to it so this may be causing a problem. Any usage problems you may be having with the forms API should be answer in those resources as I"m still a little unclear on what you're trying to accomplish... specifically with the submit button.
Old answer:
Ok if I understand this correctly
$club is not being set correctly. If
the first result from your query is
the number your looking for then this
should work.
Try calling
<?php print_r(db_fetch_array($result)) ?>
to get a look at everything returned
from the query.
I'm a little unclear as to what is
being set incorrectly. If it's
#value inside your associated array
then the culprit must be the query.
If #value is being set correctly and
whatever your doing with it later may
be the culprit (not shown here). If
its the values in your $form_state I
don't see that your using $club here
at all.
Also, in your addSR_submit_function
you don't seem to be using the $form
variable, or using $club for anything
except for setting the message which
appears at the top of the page your on
when it's called.
I may need some further clarification
as to what exactly is going wrong.
Also, when you're calling
drupal_set_message function, are you
just doing this for debugging
purposes?
Shouldn't you check
drupal_set_message($form_state['values']['field_sr_account']);
instead of
drupal_set_message($club);
in addSR_submit_function ?
OK, just a quess: not sure what type db_result returns for your query, may be it has something to do with types conversions? So this is to make sure value is int.
'#value' => (int)$club
cinqoTimo, Out of curiosity what kind of CCK field is this? Is it a Integer, Decimal, Float? and do you have any special parameters on that field not normally on by default? What is the column type in the db?
Can you post the html output of the form. That might give a clue as to what might be going on.
Are you using any javascript to edit any values for this field?
Have you tried outputting the value results from addSR_form_service_request_node_submit hook? Any difference there.
Sorry for all the questions. Just thinking out loud as it seems you have covered most of your bases.

Dynamically generate classes at runtime in php?

Here's what I want to do:
$clsName = substr(md5(rand()),0,10); //generate a random name
$cls = new $clsName(); //create a new instance
function __autoload($class_name)
{
//define that instance dynamically
}
Obviously this isn't what I'm actually doing, but basically I have unknown names for a class and based on the name, I want to generate the class with certain properties etc.
I've tried using eval() but it is giving me fits over private and $this-> references...
//edit
Ok, obviously my short and sweet "here's what I want to do" caused massive strife and consternation amongst those who may be able to provide answers. In the hope of getting an actual answer I'll be more detailed.
I have a validation framework using code hints on the site I maintain. Each function has two definitions
function DoSomething($param, $param2){
//code
}
function DoSomething_Validate(vInteger $param, vFloat $param2){
//return what to do if validation fails
}
I'm looking to add a validator for primary keys in my database. I don't want to create a separate class for EVERY table (203). So my plan was to do something like
function DoSomething_Validate(vPrimaryKey_Products $id){ }
Where the __autoload would generate a subclass of vPrimaryKey and set the table parameter to Products.
Happy now?
As of PHP 7.0, with a little creativity and knowledge of some lesser known PHP features, you can absolutely do this without resorting to eval or creating script files dynamically. You just need to use anonymous classes and class_alias(), like such:
spl_autoload_register(function ($unfoundClassName) {
{
$newClass = new class{}; //create an anonymous class
$newClassName = get_class($newClass); //get the name PHP assigns the anonymous class
class_alias($newClassName, $unfoundClassName); //alias the anonymous class with your class name
}
This works because anonymous classes are still assigned a name behind the scenes and put in the global scope, so you're free to grab that class name and alias it. Check out the second comment under the anonymous classes link above for more information.
Having said that, I feel like all the people in this question who are saying "Eval is always a very bad idea. Just don't use it ever!" are just repeating what they've heard from the hive mind and not thinking for themselves. Eval is in the language for a reason, and there are situations where it can be used effectively. If you're on an older version of PHP, eval might be a good solution here.
However, they are correct in that it can open up very large security holes and you have to be careful how you use it and understand how to eliminate the risks. The important thing is, much like SQL injection, you have to sanitize any input you put in the eval statement.
For example, if your autoloader looked like this:
spl_autoload_register(function ($unfoundClassName) {
{
eval("class $unfoundClassName {}");
}
A hacker could do something like this:
$injectionCode = "bogusClass1{} /*insert malicious code to run on the server here */ bogusClass2";
new $injectionCode();
See how this has the potential to be a security hole? Anything the hacker puts between the two bogusClass names will be run on your server by the eval statement.
If you adjust your autoloader to check the class name passed in (i.e. doing a preg_match to make sure there's no spaces or special characters, checking it against a list of acceptable names, etc.), you can eliminate these risks and then eval might be totally fine to use in this situation. If you're on PHP 7 or higher though, I recommend the anonymous class alias method above.
its funny, actually this is one of the few things where eval doesnt seem such a bad idea.
as long as you can ensure that no user input will ever enter the eval.
you still have downsides like when your using a bytecode cache that code wont be cached etc etc. but the security issues of eval are pretty much related to having user inputy in the eval, or to ending up in the wrong scope.
if you know what you are doing, eval will help you with this.
That said, in my opinion you are much better off when you no rely on type-hinting for your validation, but you have one function
DoSomething_Validate($id)
{
// get_class($id) and other validation foo here
}
I know this is an old question and there are answers that WILL work, but I wanted to offer a few snippets that would answer the original question and I think offer a more expanded solution should someone end up here like I did when searching for an answer to this problem.
Create Single Dynamic Class
<?php
// Without properties
$myclassname = "anewclassname";
eval("class {$myclassname} { }";
// With a property
$myclassname = "anewclassname";
$myproperty = "newproperty";
eval("class {$myclassname} { protected \${$myproperty}; }";
?>
As long as you properly escape your text, you could also add a function in there.
But what about if you want to dynamically create classes based on something that could itself be dynamic such as creating a class for each table in your database as the original question mentioned?
Create Multiple Dynamic Classes
<?php
// Assumes $dbh is a pdo connection handle to your MySQL database
$stmt=$dbh->prepare("show tables");
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
$handle = null;
$classcode = '';
foreach ($result as $key => $value) {
foreach ($value as $key => $value) {
$classcode = "class {$value} { ";
$stmt2=$dbh->prepare("DESC $value");
$stmt2->execute();
$result2 = $stmt2->fetchAll(PDO::FETCH_ASSOC);
foreach ($result2 as $key => $value) {
$classcode .= "public \${$value['Field']}; ";
}
$classcode .= "}";
eval($classcode);
}
}
?>
This will dynamically generate a class for each table in a database. For each class, a property that is named after each column will ALSO get created.
Now it's been pointed out you shouldn't do this. As long as you control what's going on in the eval, the risk of security isn't a problem. BUT -- there's most likely a solution that makes more sense if you think deeply enough about it. I thought I had the perfect use case for dynamically creating new classes. Careful examination of the problem proved otherwise.
One potential solution -- use the stdClass for creating objects that are just data containers and don't need any methods.
Also -- as mentioned, you could use a script to manually generate lots of classes. In the case of classes mirroring your database tables, you could use the same logic I have above and instead of doing an eval, write that info to a file.
I think using eval() it's not a reliable solution, especially if your script or software will be distributed to different clients. Shared hosting providers always disable eval() function.
I'm thinking of a better aproach like this :
<?php
function __autoload( $class ) {
require 'classes/'.$class.'.php';
}
$class = 'App';
$code = "<?php class $class {
public function run() {
echo '$class<br>';
}
".'
public function __call($name,$args) {
$args=implode(",",$args);
echo "$name ($args)<br>";
}
}';
file_put_contents('classes/App.php' ,$code);
$a = new $class();
$a->run();
After finishing executing the code, you can delete the file if you want, I tested it and it works perfectly.
Using eval() is really a bad idea. It opens a large security hole. Just don't use it!
function __autoload($class) {
$code = "class $class {`
public function run() {
echo '$class<br>';
}
".'
public function __call($name,$args) {
$args=implode(",",$args);
echo "$name ($args)<br>";
}
}';
eval($code);
}
$app=new Klasse();
$app->run();
$app->HelloWorld();
This might help to create a class at runtime.
It also creates a methor run and a catchall method for unknown methods
But better create Objects at runtime, not classes.
This is almost certainly a bad idea.
I think your time would be better spent creating a script that would create your class definitions for you, and not trying to do it at runtime.
Something with a command-line signature like:
./generate_classes_from_db <host> <database> [tables] [output dir]
Please read everyone else answers on how this is truly a very very bad idea.
Once you understand that, here is a small demo on how you could, but should not, do this.
<?php
$clname = "TestClass";
eval("class $clname{}; \$cls = new $clname();");
var_dump($cls);
I have created a package that dynamically creates classes/interfaces/traits... and stores them into a file
you can then just include the created file to be able to use your class
package : https://packagist.org/packages/kanel/enuma
We can create class instance dynamically by following way
I also face this issue in Laravel 5.8 version and now it is working fine for me.
Give full path instead of the class Name
class TestController extends Controller
{
protected $className;
public function __construct()
{
$this->className = 'User';
}
public function makeDynamicInstance()
{
$classNameWithPath = 'App\\' . $this->className;
$classInstance = new $classNameWithPath;
$data = $classInstance::select('id','email')->get();
return $data;
}
}
Output
Illuminate\Database\Eloquent\Collection Object
(
[items:protected] => Array
(
[0] => App\User Object
(
[fillable:protected] => Array
(
[0] => name
[1] => email
[2] => password
[3] => user_group_id
[4] => username
[5] => facebook_page_id
[6] => first_name
[7] => last_name
[8] => email_verified
[9] => active
[10] => mobile
[11] => user_type
[12] => alternate_password
[13] => salt
[14] => email_verification_token
[15] => parent_id
)
[hidden:protected] => Array
(
[0] => password
[1] => remember_token
)
[casts:protected] => Array
(
[email_verified_at] => datetime
)
[connection:protected] => mysql
[table:protected] => users
[primaryKey:protected] => id
[keyType:protected] => int
[incrementing] => 1
[with:protected] => Array
(
)
[withCount:protected] => Array
(
)
[perPage:protected] => 15
[exists] => 1
[wasRecentlyCreated] =>
[attributes:protected] => Array
(
[id] => 1
[email] => admin#admin.com
)
[original:protected] => Array
(
[id] => 1
[email] => admin#admin.com
)
[changes:protected] => Array
(
)
[dates:protected] => Array
(
)
[dateFormat:protected] =>
[appends:protected] => Array
(
)
[dispatchesEvents:protected] => Array
(
)
[observables:protected] => Array
(
)
[relations:protected] => Array
(
)
[touches:protected] => Array
(
)
[timestamps] => 1
[visible:protected] => Array
(
)
[guarded:protected] => Array
(
[0] => *
)
[rememberTokenName:protected] => remember_token
)
)

Categories