I'm fuzzy on the terminology, and I've tried searching - but have so far not gotten any wiser.
I'm trying to learn OOP in PHP, and I'm trying to write a configuration class that stores value/key pairs in a DB. It's working perfectly well, but not quite the way I want it to work.
Anyway, here's what I do:
$config=new Config();
$keyValue=$config->keyName; // Get key value
$config->keyName=$newKeyvalue; // Set key value
$description=$config->getDescription($keyName); // get description
$config->setDescription($keyName, $description); // set description
Here's what I want to do (only the two last lines, since those are the relevant ones):
$description=$config->keyName->description; // get description
$config->keyName->description=$newDescription; // set description
Setting/getting key values is done with _get()/_set(), but how do I set "subproperties" of a key the same way?
Edit:
Solved it. Working class with _get/_set/_isset/_unset working properly is here: http://pastebin.com/TFAA8Dcq
(Note: Little error checking is done, and this class allows for setting dynamic object names, which is probably not the safest thing in the world)
You would need to make the "keyName" property a class as well. For example, $config->keyName = new stdClass; would allow you to then set $config->keyName->property = "something else";
Obviously you could no longer set the keyName property directly to a string anymore in that case, though. You can, however, implement the __toString magic method in PHP classes to automatically return a string variable when a Class is treated implicitly as a string.
Related
I'm running into the problem that I think everyone runs into with null: that is, there are different kinds of null. It could mean "empty", "unchanged", "unset", "unknown", or any number of things. And I am to the point where I need to distinguish between them somehow.
Basically I have a database manager part of my program that receives an object representing data to be updated in the database from from another part of my program that is responsible for validating form data and converting it into said object (Which has one of a several different classes with specific predefined properties). I need some way to distinguish between a "null" property in that object which means "I actually want the value null stored in the database" and a different type of "null" which means "If there is an existing value in the database don't change it". Of the two, the latter is going to be by far the more common case, but I need some way to allow for the former as well.
My current thought, which would seem the best for this situation, is to create a new class which represents each "type" of null, for example an Unchanged class verses a SetToNull type or something like that. Then I could set the property to an instance of one of those classes.
The only issue is that I would like a function that behaves similar to the existing isset() function in that it would allow me to check both if a given property exists and also if it has a real value (as opposed to one of my new null types) in one simple statement.
I'm aware I can do something like this:
if (isset($thing->property) && !($thing->property instanceof Unchanged || $thing->property instanceof SetToNull)){
// do whatever
}
However, that's obviously not ideal because it's long and unwieldy, and I don't want to have that everywhere all over my code base. I'd prefer something like this:
if (myCustomIsset($thing->property)){
// do whatever
}
However, isset doesn't seem to be a normal function; somehow it suppresses warnings if the property hasn't been defined or something like that, so I'm not sure how to implement something like that myself.
Personally, I would not change the model. The model should represent the data in the database, and the values of its properties should be the same as the values of the columns those properties represent.
I would look at changing how the model gets its data. It sounds like you're instantiating the model from the form data, which doesn't include values for every property because the form only updates certain properties.
If you instantiate the model instead by selecting it from the database, and then modify it with the form data before validating and saving it, it will have the correct values for every property, and null will unambiguously mean null.
One thing that comes to mind which I have done in the past is two pass in two arguments. The first argument is the object by itself. The second argument is a string that contains the property name.
Here is an example:
function MyCustomIsset($object, $property) {
return isset($object->$property) && !($object->$property instanceof Unchanged || $object->$property instanceof SetToNull);
}
And here is how you would use it:
if (myCustomIsset($thing, "property")){
// do whatever
}
Since you aren't referencing the property of the object when you pass it in, PHP won't throw an error. In the body of the function, you reference the property using the passed in string argument. You can reference the property using a special feature of PHP called variable variables.
In PHP, I cannot assign a value to a variable unless I access its property without using a getter method, is it by design or I missed something?
Simply put, when I do $article->content->value[$some_value] = 'hello' it works, but $article->get_value()[$some_value] = 'hello' sets nothing, the array remains empty.
What the get_value does is just return $this->content->value, and when used as a getter, it does what it supposed to do as expected.
I feel like I missed some basic here, if someone could share me why setting value doesn't work, it'll be great.
Unlike objects, arrays aren't returned by reference in PHP, so when you call the getter method, you're getting back a copy.
If you want to modify the object property itself then you can change the method definition to return a reference by prepending the method name with an ampersand, e.g.
public function &getArray()
{
return $this->array;
}
See https://3v4l.org/1YK9H for a demo
I should stress that this is absolutely not a common pattern in PHP, other than perhaps a long way back into the PHP 4 days when OOP was a lot less ubiquitous. I certainly wouldn't expect a class I was using to return arrays by reference, and neither would I recommend anyone else doing it. Note that it's not possible to ask the class for a reference, in order to prevent unwanted modifications to private properties - the class has to define the behaviour.
The PHP documentation has more information about returning by reference here: http://php.net/manual/en/language.references.return.php
I'm having a strange issue with pdo_odbc and PDO::FETCH_OBJ (and PDO::FETCH_CLASS) that results in the following error message:
PHP Fatal error: Cannot access empty property
Here's the code:
$dbh = new PDO("odbc:FOO");
$sth = $dbh->query("
SELECT rolename
FROM dbc.allrolerights
WHERE databasename = 'BAR'
");
$result = $sth->fetch(PDO::FETCH_OBJ);
The FOO DSN, for reference, is a Teradata datasource using the tdata.so driver, provided by the tdodbc package.
I believe this is happening because the field name (as returned from the ODBC query) is blank when PDO calls zend_API.h:object_init_ex() to instantiate the stdClass object. If I switch to PDO::FETCH_LAZY and var_dump() the row, I get the following:
object(PDORow)#3 (2) {
["queryString"]=>
string(95) "
SELECT rolename
FROM dbc.allrolerights
WHERE databasename = 'BAR'
"
[""]=>
string(30) "FNAR "
}
Which seems to back it up. I've tried several different PDO attribute combinations and bunch of different angles to work around the problem. One solution is to fetch an associative array and pass it to the class constructor. However, this doesn't work for certain frameworks and ORMs that are using PDO::FETCH_CLASS directly, behind the scenes.
I want to add that other fetch methods seem to do the right thing, for example, PDO::FETCH_NAMED:
array(1) {
["RoleName"]=>
string(30) "FNAR "
}
I'm looking for something I can put in the PDO dbh or sth definition, or in the odbc.ini or odbcinst.ini for the datasource or driver, to resolve this issue. Thank you in advance.
Update: odbc_fetch_object() (i.e. not PDO) works great with the same exact everything. Just wanted to mention it. There clearly doesn't seem to be any serious issues with PHP, unixODBC, or the ODBC driver. It's something in the PDO code. Time to open a bug report... opened
$dbh = odbc_connect("FOO", NULL, NULL)
or die(odbc_error_msg());
$sth = odbc_exec($dbh, "
SELECT rolename
FROM dbc.allrolerights
WHERE databasename = 'BAR'
");
$result = odbc_fetch_object($sth);
var_dump($result);
And the output:
object(stdClass)#1 (1) {
["RoleName"]=>
string(30) "FNAR "
}
Update 2: The situation continues to grow more and more bizarre. I can do a PDO::FETCH_LAZY and see the blank column name as seen in the var_dump() above, but if I try to access the property by name (e.g. $result->RoleName), it works! What are these fetch methods doing so differently that some of them can sometimes access the field names, and others cannot?
Side-by-side comparisons of ODBC traces ("working" cf. "not working") shows no differences (other than different pointer addresses). PDO::FETCH_BOUND works with both numbered and named columns. PDO::FETCH_INTO an object with a RoleName property does not.
Your question describes two problems:
Why is it that objects cannot be created with properties having empty-string names when using PDO::FETCH_OBJ, but apparently can when using other methods?
As documented under Internal structures and implmentation in the PHP Internals Book, "dynamic properties" (i.e. an object's member variables that are not declared in its class definition, but rather are created at runtime) are implemented as a hash table.
If one wishes to populate a newly instantiated standard object with a bunch of properties that are currently held in a hash table, one can simply point the object's properties variable at that existing hash table—PHP's object_and_properties_init() function, which is called by odbc_fetch_object(), does exactly that without performing any sanity checking on the table's keys. Consequently, one can instantiate an object with strange property names (such as the empty string).
On the other hand, if one already has an instantiated object and needs to set a property value (whilst preserving any others that already exist), one must copy that value into the object's hash table—PHP's zend_std_write_property() method, which handles this action for standard objects, does exactly that having first performed a sanity check on the property name. Consequently, one cannot add a property with a strange name (such as the empty string) to an existing object.
This discrepancy in sanity-checking between the two approaches appears, to my mind, to be a bug: any restrictions on dynamic property names should be enforced irrespective of the method by which such properties are created. Whether strange names of this sort should be allowed (and therefore the sanity checking should be removed from the latter method), or disallowed (and therefore some sanity checking should be added to the former method), is a decision that I shall leave to the PHP developers.
How does PDO fit into all this?
PDOStatement::fetch() first prepares the destination into which results will be stored and then iterates over the columns storing each field in turn: I imagine it does this in order to simplify the codebase, as each fetch style can be implemented within the same structure. However, this does mean that when called using the PDO::FETCH_OBJ style (and also both PDO::FETCH_CLASS and PDO::FETCH_INTO, as you have observed), an object is instantiated first and its properties are populated later. Consequently, strange property names (such as the empty string) result in the observed failure.
The other fetch styles that you have tried don't experience the same problem because:
PDO::FETCH_BOUND fetches into variables that were specified by a previous call to PDOStatement::bindColumn(), so PHP never attempts to write to a property having an empty name;
PDO::FETCH_LAZY skips the whole shebang and does things similar manner to odbc_fetch_object() above.
Similarly, array-based fetch styles won't suffer similar problems because empty-string keys are perfectly valid in those hash tables.
Why is it that PDO thinks the column names in this ODBC recordset are empty strings?
The answer to this is much less obvious to me.
We saw earlier that, to populate properties, PDO uses stmt->columns[i].name as the property name. This should have been correctly filled at an earlier point, when pdo_stmt_describe_columns() was called. This function in turn had called the driver's describer method for each column in the resultset: in the case of PDO_ODBC, that's odbc_stmt_describe() which does indeed assign a value to that field.
So, everything looks fine on the PHP side. It would be interesting to know whether the call to the driver's SQLDescribeCol() function correctly populated the column name into the buffer provided as its third argument: one imagines not, which would suggest that the problem lies in the ODBC driver itself. You mentioned that you're using Teradata: but are you sure that you are using the same driver for both PDO_ODBC (which isn't working) and ext/odbc (which is)?
In particular, Teradata document under Extension Level Functions:
By default, SQLDescribeCol and SQLColAttribute return the column name instead of the Teradata column title. If an application wants ODBC Driver for Teradata to return the column title instead of the actual column name, then the option Use Column Names in the Teradata ODBC Driver Options dialog box must not be selected for the DSN used, or set DontUseTitles = No on the UNIX OS.
Returning the column title instead of the actual column name can cause problems for certain applications, such as Crystal Reports, because they expect to get the column name and not the column title.
I think a "solution" by now wold be use the fetch style PDO::FETCH_NAMED and then convert the returned array and populate dynamic class:
function arrayToObject(array $array){
$obj = new stdClass();
foreach($array as $k => $v)
$obj->$k = $v;
return $obj;
}
Before I say what I'm attempting to do, I want to preface it with me being open to any/all solutions.
Basically I'm editing a PHP file. We have a localization function in the class I'm editing that takes a key, similar to this:
$this->localize('SOME_KEY_HERE');
What I'm looking to do (in VIM) is create a keybinding that will determine what the localization value of that function call is, when the cursor is over that key. I can get the key easily enough into a vimscript function. My plan was then to just find the class name via regex (this doesn't need to be perfect) and then instantiate that class via :exec php -r and call the function. So more or less, create a minimal script that would actually make the call.
Like I said, I can get the key isolated the way I want. But trying to figure out the proper vimscript way to find the current class. Basically I want the second word on the first line that matches /^class / in the current file, in a vimscript variable. I've browsed some of the documentation but can't seem to get it to work.
Open to any/all suggestions on how to perform this operation.
Edit: To clarify, I can isolate the key I'm highlighting by doing something like:
noremap <leader>z :call DoSomething(expand("<cword>"))<CR>
function! DoSomething(l)
let key = a:l
echom key
endfunction
You can get the name of the current class with:
function! DoSomething(l)
let key = a:l
search('^class\s\zs\w\+\ze', 'b')
let class = expand('<cword>')
echo 'key: ' . key . ' class: ' . class
endfunction
You still need to deal with the position of the cursor.
I've read about this interesting syntax in PHP:
$value = (new MyClass)->attribute1;
Is it ok to use it? I've never seen anything like this in any code I've analyzed. Any pros and cons?
Why can't I set the attribute using this syntax? Structures like this:
(new MyClass)->attribute1 = 'value1';
throw errors at '=' sign, no matter if the attribute exists in the class already.
Well i don't see the point of using it since you loose your reference to the object, you cannot use it anymore, and it breaks the OO concept.
I think (new MyClass)->attribute1 is resolved first, so it is the same as writing something like 42 = 12
This may have a sense, if the class MyClass supports internal static list (or hashmap) of all existing instances. This way you can create new object and get its unique ID or index in the list for future references (for example, by sending it via cookies to a client).
As for assignment, you'd post exact error message for this case. I can guess, that the error is about assigning something to a temporary value which is about to be destroyed.