Related
I am trying to alert a returned value from a function and I get this in the alert:
[object Object]
Here is the JavaScript code:
<script type="text/javascript">
$(function ()
{
var $main = $('#main'),
$1 = $('#1'),
$2 = $('#2');
$2.hide(); // hide div#2 when the page is loaded
$main.click(function ()
{
$1.toggle();
$2.toggle();
});
$('#senddvd').click(function ()
{
alert('hello');
var a=whichIsVisible();
alert(whichIsVisible());
});
function whichIsVisible()
{
if (!$1.is(':hidden')) return $1;
if (!$2.is(':hidden')) return $2;
}
});
</script>
whichIsVisible is the function which I am trying to check on.
As others have noted, this is the default serialisation of an object. But why is it [object Object] and not just [object]?
That is because there are different types of objects in Javascript!
Function objects:
stringify(function (){}) -> [object Function]
Array objects:
stringify([]) -> [object Array]
RegExp objects
stringify(/x/) -> [object RegExp]
Date objects
stringify(new Date) -> [object Date]
… several more …
and Object objects!
stringify({}) -> [object Object]
That's because the constructor function is called Object (with a capital "O"), and the term "object" (with small "o") refers to the structural nature of the thingy.
Usually, when you're talking about "objects" in Javascript, you actually mean "Object objects", and not the other types.
where stringify should look like this:
function stringify (x) {
console.log(Object.prototype.toString.call(x));
}
The default conversion from an object to string is "[object Object]".
As you are dealing with jQuery objects, you might want to do
alert(whichIsVisible()[0].id);
to print the element's ID.
As mentioned in the comments, you should use the tools included in browsers like Firefox or Chrome to introspect objects by doing console.log(whichIsVisible()) instead of alert.
Sidenote: IDs should not start with digits.
[object Object] is the default toString representation of an object in javascript.
If you want to know the properties of your object, just foreach over it like this:
for(var property in obj) {
alert(property + "=" + obj[property]);
}
In your particular case, you are getting a jQuery object. Try doing this instead:
$('#senddvd').click(function ()
{
alert('hello');
var a=whichIsVisible();
alert(whichIsVisible().attr("id"));
});
This should alert the id of the visible element.
You can see value inside [object Object] like this
Alert.alert( JSON.stringify(userDate) );
Try like this
realm.write(() => {
const userFormData = realm.create('User',{
user_email: value.username,
user_password: value.password,
});
});
const userDate = realm.objects('User').filtered('user_email == $0', value.username.toString(), );
Alert.alert( JSON.stringify(userDate) );
reference
https://off.tokyo/blog/react-native-object-object/
Basics
You may not know it but, in JavaScript, whenever we interact with string, number or boolean primitives we enter a hidden world of object shadows and coercion.
string, number, boolean, null, undefined, and symbol.
In JavaScript there are 7 primitive types: undefined, null, boolean, string, number, bigint and symbol. Everything else is an object. The primitive types boolean, string and number can be wrapped by their object counterparts. These objects are instances of the Boolean, String and Number constructors respectively.
typeof true; //"boolean"
typeof new Boolean(true); //"object"
typeof "this is a string"; //"string"
typeof new String("this is a string"); //"object"
typeof 123; //"number"
typeof new Number(123); //"object"
If primitives have no properties, why does "this is a string".length return a value?
Because JavaScript will readily coerce between primitives and objects. In this case the string value is coerced to a string object in order to access the property length. The string object is only used for a fraction of second after which it is sacrificed to the Gods of garbage collection – but in the spirit of the TV discovery shows, we will trap the elusive creature and preserve it for further analysis…
To demonstrate this further consider the following example in which we are adding a new property to String constructor prototype.
String.prototype.sampleProperty = 5;
var str = "this is a string";
str.sampleProperty; // 5
By this means primitives have access to all the properties (including methods) defined by their respective object constructors.
So we saw that primitive types will appropriately coerce to their respective Object counterpart when required.
Analysis of toString() method
Consider the following code
var myObj = {lhs: 3, rhs: 2};
var myFunc = function(){}
var myString = "This is a sample String";
var myNumber = 4;
var myArray = [2, 3, 5];
myObj.toString(); // "[object Object]"
myFunc.toString(); // "function(){}"
myString.toString(); // "This is a sample String"
myNumber.toString(); // "4"
myArray.toString(); // "2,3,5"
As discussed above, what's really happening is when we call toString() method on a primitive type, it has to be coerced into its object counterpart before it can invoke the method.
i.e. myNumber.toString() is equivalent to Number.prototype.toString.call(myNumber) and similarly for other primitive types.
But what if instead of primitive type being passed into toString() method of its corresponding Object constructor function counterpart, we force the primitive type to be passed as parameter onto toString() method of Object function constructor (Object.prototype.toString.call(x))?
Closer look at Object.prototype.toString()
As per the documentation,
When the toString method is called, the following steps are taken:
If the this value is undefined, return "[object Undefined]".
If the this value is null, return "[object Null]".
If this value is none of the above, Let O be the result of calling toObject passing the this value as the argument.
Let class be the value of the [[Class]] internal property of O.
Return the String value that is the result of concatenating the three Strings "[object ", class, and "]".
Understand this from the following example
var myObj = {lhs: 3, rhs: 2};
var myFunc = function(){}
var myString = "This is a sample String";
var myNumber = 4;
var myArray = [2, 3, 5];
var myUndefined = undefined;
var myNull = null;
Object.prototype.toString.call(myObj); // "[object Object]"
Object.prototype.toString.call(myFunc); // "[object Function]"
Object.prototype.toString.call(myString); // "[object String]"
Object.prototype.toString.call(myNumber); // "[object Number]"
Object.prototype.toString.call(myArray); // "[object Array]"
Object.prototype.toString.call(myUndefined); // "[object Undefined]"
Object.prototype.toString.call(myNull); // "[object Null]"
References:
https://es5.github.io/x15.2.html#x15.2.4.2
https://es5.github.io/x9.html#x9.9
https://javascriptweblog.wordpress.com/2010/09/27/the-secret-life-of-javascript-primitives/
It's the value returned by that object's toString() function.
I understand what you're trying to do, because I answered your question yesterday about determining which div is visible. :)
The whichIsVisible() function returns an actual jQuery object, because I thought that would be more programmatically useful. If you want to use this function for debugging purposes, you can just do something like this:
function whichIsVisible_v2()
{
if (!$1.is(':hidden')) return '#1';
if (!$2.is(':hidden')) return '#2';
}
That said, you really should be using a proper debugger rather than alert() if you're trying to debug a problem. If you're using Firefox, Firebug is excellent. If you're using IE8, Safari, or Chrome, they have built-in debuggers.
[object Object] is the default string representation of a JavaScript Object. It is what you'll get if you run this code:
alert({}); // [object Object]
You can change the default representation by overriding the toString method like so:
var o = {toString: function(){ return "foo" }};
alert(o); // foo
I think the best way out is by using JSON.stringify() and passing your data as param:
alert(JSON.stringify(whichIsVisible()));
You have a javascript object
$1 and $2 are jquery objects, maybe use alert($1.text()); to get text or alert($1.attr('id'); etc...
you have to treat $1 and $2 like jQuery objects.
You are trying to return an object. Because there is no good way to represent an object as a string, the object's .toString() value is automatically set as "[object Object]".
Consider the following example:
const foo = {};
foo[Symbol.toStringTag] = "bar";
console.log("" + foo);
Which outputs
[object bar]
Basically, any object in javascript can define a property with the tag Symbol.toStringTag and override the output.
Behind the scenes construction of a new object in javascript prototypes from some object with a "toString" method. The default object provides this method as a property, and that method internally invokes the tag to determine how to coerce the object to a string. If the tag is present, then it's used, if missing you get "Object".
Should you set Symbol.toStringTag? Maybe. But relying on the string always being [object Object] for "true" objects is not the best idea.
The object whose class is Object seems quite different from the usual class instance object, because it acts like an associative array or list: it can be created by simple object literals (a list of keys and properties), like this: let obj={A:'a',B:'b'}; and because it looks very like this same literal notation when displayed in the Developer Tools Console pane and when it is converted to a JSON string.
But, in fact, the only real difference in objects of other classes (which are derived or extended from Object) is that other classes usually have constructors and methods (these are all functions), in addition to properties (which are variables). A class instance object is allocated using the 'new' operator, and its properties and methods are accessible through the 'this' variable. You can also access the underlying static functions that are copied to each new instance by using the 'prototype' property, and even extend system classes by adding new functions to their prototype object.
The Array object is also derived from Object and is frequently used: it is an ordered, 0-indexed array of variable values.
Object objects, unlike Arrays and other classes are treated simply as associative arrays (sometimes considered ordered, and sometimes considered unordered).
I am trying to implement the "Edit Application Settings" feature. After a bit of thinking, my configuration values are stored in the DB with key -> value structure, like this:
id
key
value
1
logo_path
img/logo.png
As you can see, for each setting, there is only a key & value column. I made an App Service provider to cache them forever, and a helper function (config('setting_key')) to get the value, but now I'd like to update it in the most efficient way.
The user interface consists of the <form action="post" ...> and input with a corresponding name, like this: <input name="setting_key_name" ... />. As you can see, the name attribute here has the value of the key column value and the actual value of the input would be the value column value (a bit of confusion here).
First thing that came to my mind, was to make a foreach loop and find & update every row in DB, but IMHO it is very unoptimized way, cause if the page has a form with 10 values, it is 10 SQL queries. But till now, this is what I've done:
$keys = collect($request->except('_token'))->keys()->toArray();
// get all settings if the key name matches the request's input name
$setting = Setting::whereIn('key', $keys)->get();
$logo = self::GENERAL_APP_LOGO; // contant with a key-name (general_application_logo);
if($request->has(self::GENERAL_APP_LOGO) && $request->$logo) {
// Processing uploaded image here;
$this->uploadLogo($image, self::LOGO_IMAGE_PATH, $name); // Using an upload trait
$setting->where('key', $logo)->value = self::LOGO_IMAGE_PATH . $name; // just a try to update the DB this way
}
foreach ($keys as $key) {
$setting->where('key', $key)->value = $request->$key; // putting all request's input values to corresponding key
}
$setting->save(); // saving the DB.
As you can see, this won't work and will throw an Exception, like Call to undefined method ...\Eloquent\Builder::save(). I tried the same code with an update, but the difficult part here is to update it multiple times (since the if section should have the update as well, for the logo), as well as binding the key to value.
So, a little bit of your help would be appreciated - what the logic should be here? How can I update a DB rows with corresponding column's value? I mean - like this (update where key = 'general_app_name' set value, 'some_setting_value'), but using the optimized and clear way?
Working solution
As #miken32 stated in his answer, I used hid version of code, but with slight changes:
// Changed the $request->settings->keys() to PHP native method array_keys():
$settings = Settings::whereIn('key', array_keys($request->settings))->get()->groupBy('id');
// Also, here I changed the `whereIn('id', ...)` to `whereIn('key', ...)`, since it was my primary index.
foreach ($request->settings as $k=>$v) {
if ($k === self::GENERAL_APP_LOGO_ID) {
// not sure about this one, but I think this is
// how you'd access a file input in an array
$image = $request->file('settings')[$k];
$this->uploadLogo($image, self::LOGO_IMAGE_PATH, $name);
$v = self::LOGO_IMAGE_PATH . $name;
}
// take the Setting object out of the list we pulled
// Here I added the ->first() to get the first element from the retrieved collection;
$setting = $settings->get($k)->first();
$setting->value = $v;
$setting->save();
}
Since I was fetching the configuration values via helper, that only returns the value of the current key (and no id column), I changed the id to key and made the key as my PK in a model. Works like a charm!
With each setting in a separate row, there's no way to avoid multiple database queries – one to get the current values for all settings, and other to update each one. Looking up items by primary key is more efficient, so I'd recommend putting the contents of the id column in your blade view, like this:
<label for="setting_{{$setting->id}}">{{$setting->key}}</label>
<input name="settings[{{$setting->id}}]" id="setting_{{$setting->id}}" value="{{$setting->value}}"/>
Now in your controller, $request->settings will be an array you can loop through. You can continue treating your file upload separately, but now you've got the id column to look up, so change your constant to that.
$settings = Settings::whereIn('id', $request->settings->keys())->get()->groupBy('id');
foreach ($request->settings as $k=>$v) {
if ($k === self::GENERAL_APP_LOGO_ID) {
// not sure about this one, but I think this is
// how you'd access a file input in an array
$image = $request->file('settings')[$k];
$this->uploadLogo($image, self::LOGO_IMAGE_PATH, $name);
$v = self::LOGO_IMAGE_PATH . $name;
}
// take the Setting object out of the list we pulled
$setting = $settings->get($k);
$setting->value = $v;
$setting->save();
}
Note that Laravel does offer methods to bulk-update multiple models at once, but they are doing separate queries to the database in the background. IIRC, the save() method doesn't do anything if the value hasn't changed, which will spare you some hits.
You could try creating a text field, or a json field if your database supports it, and storing all of your settings as a JSON string in that field.
id
settings
1
{ "logo_path" : "img/logo.png", "foo" : "bar", "thing_count" : 17 }
2
{ "logo_path" : "img/logo2.png", "foo" : "baz", "thing_count" : 4 }
In your Laravel model, you can cast it as an array
protected $casts = ["settings" => "array"];
and then use it from the model
echo $theModel->settings['logo'];
echo $theModel->settings['foo'];
or you can cast it as a fully fledged object if you need to using value object casting.
One gotcha that can be confusing for people is the setting of the values in the array to update it. This will not work:
$theModel->settings['foo'] = "boz";
The reason is due to the way the Laravel mutators work. Instead, you make a value copy of the settings, change that, and reassign it to the model:
$settings = $theModel->settings;
$settings['foo'] = "boz";
$theModel->settings = $settings;
This approach has the capacity to infinitely expandable in the future as you just add new keys to your json. Be sure to do checks on the settings array to ensure fields you are looking for are set (which is why value objects can be very handy to do validation).
It also solves your database query problem - it's only ever one.
You don't need to put
$setting->where('key', $logo)->value = ...;
Just call
$setting->where('key', $logo)->update($request->toArray());
$setting->save(); called when you instantiated setting class like :
$setting = new Setting();
Or
$setting = Setting::whereIn('key', $keys)->get()->first();
Then
$setting->val = ...;
$setting->save(); // then it work's
I am testing an parameter sent to a mocked event handler. The parameter is an object of "Event" sub-type, which itself has some data nested inside it. I want to test the Event and its substructure matches the fixture data I've injected into the code through various mocks.
I can test the "top level" of the event easily enough: the classname, and simple attributes like an event name string. I can also test that an attribute contains the same object, which I believe implicitly tests all the substructure of the object.
The problem I'm having is some of the sub-structure in a more complex example is causing the test to fail but it's irrelevant, so I want to cherry-pick specific properties of the sub-structure, and not just identity-compare the entire object.
I feel like I'm missing something in the attribute assertions: how to access the parameter that the "with" refers to - as variable. Then I could pass it into some of the assert methods like attributeEqualTo which require the item under test to be passed in. Perhaps these just cannot be used in the fluent case I'm using?
I'd like to check the event.data is a certain class.
I'd like to check the event.data.thing1 == X
I'd like to check the event.data.thing2 == Y
and so on.
Simplified code:
class MyEventData{
public $thing1;
public $thing2;
}
class MyEvent{
public $data;
}
// An event gets fired containing this in the tests
$eventData = new MyEventData(1,2);
$this->eventMock->expects($this->exactly(3))
->method('fire')
->with(
$this->logicalAnd(
// THIS WORKS OK
$this->isInstanceOf('\MyApp\MyEvents\SomeEvent'),
// THIS WORKS OK
$this->attributeEqualTo ('name', SomeEvent::EVENT_NAME),
// THIS WORKS in simplified cases only
$this->attributeEqualTo ('data', $eventData),
// HOW DO I GET THE "WITH" PARAMETER CONTEXT "INTO" THE THIRD PARAMETER?
$this->assertAttributeInstanceOf('\MyApp\MyEvents\MyEventData', 'data', -classOrObject- ),
// Then how can I test with attribute data.thing1 == 1 and data.thing2 = 2
)
);
I've got it to work using the callback constraint, but it feels like I've now stepped off the path and lost the power of PHPUnit - I can't seem to use the assertion helpers here anymore.
e.g. If the accumulated tests return false, I don't get any details in the output log beyond "Expectation failed for ... and is accepted by specified callback".
$this->callback(function($subject){
$b = true;
// I tried using this constraint but can't access (autoload) this class? So is it not supposed to be used directly?
//$c = new PHPUnit_Framework_Constraint_IsInstanceOf('\MyApp\MyEvents\MyEventData');
// return $c->matches(subject);
// this is the right assert, but it doesn't return the result, so I cannot use it in a callback constraint.
\PHPUnit_Framework_Assert::assertAttributeInstanceOf('\MyApp\MyEvents\MyEventData', 'data', $subject);
// This works but seems very "Manual"
$b = $b && get_class($subject->data) == '\MyApp\MyEvents\MyEventData';
$b = $b && $subject->data->thing1 == 1;
$b = $b && $subject->data->thing2 == 1;
return $b;
})
I'm developping a website, where if a user changes some data, it should be stored on the background, to see who did last change and what etc... . I have 1 object called Event, but the data onscreen is devided into 2 tabs (Client and Event). After the submit, I get all the fields and put the data in the object. I have this self made function to compare the values in the new boject with the values of the old object:
function createArrayReturnDiff($obj1, $obj2) {
$helpArray1 = (array) $obj1; //convert object to array
$helpArray2 = (array) $obj2; //convert object to array
$help = array_diff_assoc($helpArray2, $helpArray1); //Computes the difference of arrays with additional index check
return $help;
}
Now this works all fine, I get an array returned with names of the field and the new value.
But here comes the tricky part. After the return of this array, I loop trough it I want to check which tab the value was on in order to give beter user feedback later. So if the value is on Cleint or Event tab. Now I made 2 arrays where I describe all the fields in each tab.
$tabKlant = array('Evenementfirmanaam', 'Evenementaanspreking', 'Evenementcontactpersoon', 'Evenementcontactpersoonstraat', 'Evenementcontactpersoongemeente', 'Evenementcontactpersoonland', 'Evenementcontactpersoonmail', 'Evenementcontactpersoontel', 'Evenementgeldigheidsdatum', 'Evenementfacturatiegegevens', 'Evenementfactuur_mededeling', 'Evenementbestelbon', 'Evenementreferentie');
$tabEvenement = array('Evenementstartdatum', 'Evenementeinddatum', 'Evenementnaam', 'Evenementfeestlocatie', 'Evenementcontactfeestlocatie', 'Evenementaantal', 'Evenementact_speeches_opm', 'Evenementdj', 'Evenementinleiding');
Now my code to check:
foreach ($help as $key => $value) {
if (in_array($key, $tabEvent)) {
$tab = "Event";
} else if (in_array($key, $tabClient)) {
$tab = "Client";
} else {
$tab = "";
}
}
Now what I tried to change was Evenementfirmanaam, so the $help array contains values with key = Evenementfirmanaam and value = 'xxxx'. Everything looks like it is supposed to work. But for some reason, it can't find the value in the in_array of my foreach.
After I tried to write away data to the database. I used a mysqli_real_escape_string on the $key of my help array (firmanaam in this case) and I found out it is creating the string like: '\0Evenement\0firmanaam' . I have no idea why the \0 are added, but I have a feeling this is the reason why the in_array function won't compare my values properly. Does anyone have an idea what the problem might be?
The problem is that the firmanaam property of your Evenement class (which $obj1 and $obj2 look like to be instances of) is private, which results in the cast to array creating special keys:
If an object is converted to an array, the result is an array whose
elements are the object's properties. The keys are the member variable
names, with a few notable exceptions: integer properties are
unaccessible; private variables have the class name prepended to the
variable name; protected variables have a '*' prepended to the
variable name. These prepended values have null bytes on either side.
This can result in some unexpected behaviour.
In essence, you are being punished for violating the logical design of your class: if $firmanaam is private the outside world should not have any access to its value. The cast to array does allow you to get the value but you really should not do this.
Since you are using Evenement to encapsulate and hide data members, do it all the way. If you want access to those members, provide for and use a getter. If you want to compare two instances with specific semantics, add a comparison method to the class.
I'm designing a class that defines a highly complex object with a ton (50+) of mostly optional parameters, many of which would have defaults (eg: $type = 'foo'; $width = '300'; $interactive = false;). I'm trying to determine the best way to set up the constructor and instance/class variables in order to be able to:
make it easy to use the class
make it easy to auto-document the class (ie: using phpDocumentor)
code this elegantly
In light of the above, I don't want to be passing the constructor a ton of arguments. I will be passing it a single hash which contains the initialization values, eg: $foo = new Foo(array('type'=>'bar', 'width'=>300, 'interactive'=>false));
In terms of coding the class, I still feel like I would rather have...
class Foo {
private $_type = 'default_type';
private $_width = 100;
private $_interactive = true;
...
}
...because I believe this would facilitate documentation generation (you get the list of the class' properties, which lets the API user know what 'options' they have to work with), and it "feels" like the right way to do it.
But then you run into the problem of mapping the incoming parameters in the constructor to the class variables, and without exploiting the symbol table, you get into a "brute force" approach which to me defeats the purpose (though I'm open to other opinions). E.g.:
function __construct($args){
if(isset($args['type'])) $_type = $args['type']; // yuck!
}
I've considered creating a single class variable that is itself an associative array. Initializing this would be really easy then, e.g.:
private $_instance_params = array(
'type' => 'default_type',
'width' => 100,
'interactive' => true
);
function __construct($args){
foreach($args as $key=>$value){
$_instance_params[$key] = $value;
}
}
But this seems like I'm not taking advantage of native features like private class variables, and it feels like documentation generation will not work with this approach.
Thanks for reading this far; I'm probably asking a lot here, but I'm new to PHP and am really just looking for the idiomatic / elegant way of doing this. What are your best practices?
Addendum (details about this particular Class)
It's quite likely that this class is trying to do too much, but it is a port of an old Perl library for creating and processing forms. There's probably a way of dividing the configuration options to take advantage of inheritance and polymorphism, but it may actually be counter-productive.
By request, here is a partial listing of some of the parameters (Perl code). You should see that these don't map very well to sub-classes.
The class certainly has getters and setters for many of these properties so the user can over-ride them; the objective of this post (and something the original code does nicely) is to provide a compact way of instantiating these Form objects with the required parameters already set. It actually makes for very readable code.
# Form Behaviour Parameters
# --------------------------
$self->{id}; # the id and the name of the <form> tag
$self->{name} = "webform"; # legacy - replaced by {id}
$self->{user_id} = $global->{user_id}; # used to make sure that all links have the user id encoded in them. Usually this gets returned as the {'i'} user input parameter
$self->{no_form}; # if set, the <form> tag will be omitted
$self->{readonly}; # if set, the entire form will be read-only
$self->{autosave} = ''; # when set to true, un-focusing a field causes the field data to be saved immediately
$self->{scrubbed}; # if set to "true" or non-null, places a "changed" radio button on far right of row-per-record forms that indicates that a record has been edited. Used to allow users to edit multiple records at the same time and save the results all at once. Very cool.
$self->{add_rowid}; # if set, each row in a form will have a hidden "rowid" input field with the row_id of that record (used primarily for scrubbable records). If the 'scrubbed' parameter is set, this parameter is also automatically set. Note that for this to work, the SELECT statement must pull out a unique row id.
$self->{row_id_prefix} = "row_"; # each row gets a unique id of the form id="row_##" where ## corresponds to the record's rowid. In the case of multiple forms, if we need to identify a specific row, we can change the "row_" prefix to something unique. By default it's "row_"
$self->{validate_form}; # parses user_input and validates required fields and the like on a form
$self->{target}; # adds a target window to the form tag if specified
$self->{focus_on_field}; # if supplied, this will add a <script> tag at the end of the form that will set the focus on the named field once the form loads.
$self->{on_submit}; # adds the onSubmit event handler to the form tag if supplied
$self->{ctrl_s_button_name}; # if supplied with the name of the savebutton, this will add an onKeypress handler to process CTRL-S as a way of saving the form
# Form Paging Parameters
# ----------------------
$self->{max_rows_per_page}; # when displaying a complete form using printForm() method, determines the number of rows shown on screen at a time. If this is blank or undef, then all rows in the query are shown and no header/footer is produced.
$self->{max_pages_in_nav} = 7; # when displaying the navbar above and below list forms, determines how many page links are shown. Should be an odd number
$self->{current_offset}; # the current page that we're displaying
$self->{total_records}; # the number of records returned by the query
$self->{hide_max_rows_selector} = ""; # hide the <select> tag allowing users to choose the max_rows_per_page
$self->{force_selected_row} = ""; # if this is set, calls to showPage() will also clear the rowid hidden field on the form, forcing the first record to be displayed if none were selected
$self->{paging_style} = "normal"; # Options: "compact"
We can, of course, allow ourselves to be drawn into a more lengthy debate around programming style. But I'm hoping to avoid it, for the sanity of all involved! Here (Perl code, again) is an example of instantiating this object with a pretty hefty set of parameters.
my $form = new Valz::Webform (
id => "dbForm",
form_name => "user_mailbox_recip_list_students",
user_input => \%params,
user_id => $params{i},
no_form => "no_form",
selectable => "checkbox",
selectable_row_prefix => "student",
selected_row => join (",", getRecipientIDsByType('student')),
this_page => $params{c},
paging_style => "compact",
hide_max_rows_selector => 'true',
max_pages_in_nav => 5
);
I can think of two ways of doing that. If you want to keep your instance variables you can just iterate through the array passed to the constructor and set the instance variable dynamically:
<?php
class Foo {
private $_type = 'default_type';
private $_width = 100;
private $_interactive = true;
function __construct($args){
foreach($args as $key => $val) {
$name = '_' . $key;
if(isset($this->{$name})) {
$this->{$name} = $val;
}
}
}
}
?>
When using the array approach you don't really have to abandon documentation. Just use the #property annotations in the class body:
<?php
/**
* #property string $type
* #property integer $width
* #property boolean $interactive
*/
class Foo {
private $_instance_params = array(
'type' => 'default_type',
'width' => 100,
'interactive' => true
);
function __construct($args){
$this->_instance_params = array_merge_recursive($this->_instance_params, $args);
}
public function __get($name)
{
return $this->_instance_params[$name];
}
public function __set($name, $value)
{
$this->_instance_params[$name] = $value;
}
}
?>
That said, a class with 50 member variables is either only used for configuration (which can be split up) or it is just doing too much and you might want to think about refactoring it.
Another approach is to instantiate the class with a FooOptions object, acting solely as an options container:
<?php
class Foo
{
/*
* #var FooOptions
*/
private $_options;
public function __construct(FooOptions $options)
{
$this->_options = $options;
}
}
class FooOptions
{
private $_type = 'default_type';
private $_width = 100;
private $_interactive = true;
public function setType($type);
public function getType();
public function setWidth($width);
public function getWidth();
// ...
}
Your options are well documented and you have an easy way to set/retrieve them. This even facilitates your testing, as you can create and set different options objects.
I don't remember the exact name of this pattern, but I think it's Builder or Option pattern.
Just to follow up with how I implemented this, based on one of Daff's solutions:
function __construct($args = array()){
// build all args into their corresponding class properties
foreach($args as $key => $val) {
// only accept keys that have explicitly been defined as class member variables
if(property_exists($this, $key)) {
$this->{$key} = $val;
}
}
}
Improvement suggestions welcomed!
You also could make a parent class.
In that class you only define the variables.
protected function _SetVarName( $arg ){
$this->varName=$arg;
}
Then extend that class into a new file and in that file you create all your processes.
So you get
classname.vars.php
classname.php
classname extends classnameVars {
}
Because most will be on default you only have to Set/Reset the ones you need.
$cn=new classname();
$cn->setVar($arg);
//do your functions..
I use this on a few of my classes. Makes it easy to copy and paste for rapid development.
private $CCNumber, $ExpMonth, $ExpYear, $CV3, $CardType;
function __construct($CCNumber, $ExpMonth, $ExpYear, $CV3, $CardType){
$varsValues = array($CCNumber, $ExpMonth, $ExpYear, $CV3, $CardType);
$varNames = array('CCNumber', 'ExpMonth', 'ExpYear', 'CV3', 'CardType');
$varCombined = array_combine($varNames, $varsValues);
foreach ($varCombined as $varName => $varValue) {$this->$varName = $varValue;}
}
Steps to use:
Paste in and get the list of variables from your current __construct function, removing any optional parameter values
If you haven't already, paste that in to declare your variables for your class, using the scope of your choosing
Paste that same line into the $varValues and $varNames lines.
Do a text replace on ", $" for "', '". That'll get all but the first and last that you'll have to manually change
Enjoy!
Just a little improvement on Daff's first solution to support object properties that may have a null default value and would return FALSE to the isset() condition:
<?php
class Foo {
private $_type = 'default_type';
private $_width = 100;
private $_interactive = true;
private $_nullable_par = null;
function __construct($args){
foreach($args as $key => $val) {
$name = '_' . $key;
if(property_exists(get_called_class(),$name))
$this->{$name} = $val;
}
}
}
}
?>