Setting Properties via Methods - php

I am new to OOP.
I am currently working on adding data to an object to then submit it to a database. I have created a method called setData to take two arguments, and then add those arguments into the object.
Inside my class, I use this
public function setData($value1, $value2) {
$this->$value1 = $value2;
}
Where on a form submit, that function is used to store data
$sites = new AddWebsites;
$sites->setData('name', $name);
$sites->setData('page_rank', $pr);
$sites->setData('pa', $pa);
$sites->setData('da', $da);
$sites->setData('tf', $tf);
$sites->setData('cf', $cf);
$sites->setData('keywords', $keywords);
$sites->setData('notes', $notes);
This will output the following data
AddWebsites Object
(
[name] => asdf.com
[page_rank] => 5
[pa] => 15
[da] => 25
[tf] => 14
[cf] => 62
[keywords] => Array
(
[0] => kw1
[1] => kw2
[2] => kw3
[3] => kw4
[4] => kw5
)
[notes] => asdf
)
I have been told that this is wrong, and will throw errors.
I was wondering if there is a better way to achieve this, if it is actually wrong, and if there is an easier way to do this.
With error reporting enabled, I have not run across anything that tells me what I am doing is wrong.
Thanks for your time.

It's wrong in pure OOP terms because you're using PHP's (somewhat unusual) ability to add arbitrary attributes to instantiated objects via your setData method.
What you should be doing - to achieve the goals of encapsulation and data validation - is something like this :
class AddWebsites {
private $name;
private $pageRank;
// etc
// Setters
public function setName(value) {
// you can put validation logic in here
this->name = value;
}
public function setPageRank(value) {
// you can put validation logic in here
this->pageRank = value;
}
// etc
// getters
public function getName() {
return this->name;
}
public function getPageRank() {
return this->pageRank;
}
}
This is using "Getters" and "Setters".
You could however have your members as public then you wouldn't need the getters

One of things i can notice is passing field name in function parameter is not an good idea. Reason behind that is if you by mistake pass wrong field name then php will create one more field for that object.
So if you are having multiple objects of same class some will have that field some will not. This leads to inconsistency.
So I feel this is not correct thing to do as you are not suppose to create properties of class pbject dynamically.
Ideal way is to have different getter and setter functions for each field and fields should be private in scope, so that you/developer will not not able to create new fields by mistake.

Related

Why does my function output " the method 'forTemplate' does not exist on 'SilverStripe\View\ArrayData"

This will be my very first question on here. I hope I give you all the info you need.
I am running a personal project using silverstripe v4.8
In my template, I have a optionsetfield, which is basically just a radiofield.
The first 3 items in the options, I have hardcoded.
I wrote another function for the rest to basically get me all the names of people who can make events, loop through them, and add them as options.
When I dump my outcome, it seems to come out the way I want it:
array (size=1)
'Rick' => string 'Rick' (length=4)
But when I try to see it in my template, it gives me:
Object->__call(): the method 'forTemplate' does not exist on 'SilverStripe\View\ArrayData'
Now when I don't add the function to my Optionset, the first 3 hardcoded items work fine.
I will post my OptionsetField and the other function below.
Thank you in advance
public function createEventsFilterForm()
{
$form = Form::create();
$form = FieldList::create([
OptionsetField::create('Eventfilters')
->setTitle('')
->setSource([
'past' => 'Verlopen',
'today' => 'Vandaag',
'future' => 'Toekomst',
$this->getFirstNameOfEvents()
])
]);
return $form;
}
public function getFirstNameOfEvents()
{
$allEvents = UpcomingEvents::get();
foreach ($allEvents as $event) {
$firstName = 'NVT';
$memberProfileID = $event->MemberProfileID;
if ($memberProfileID) {
$firstName = [MemberProfile::get()->byID($memberProfileID)->FirstName];
}
$output = ArrayLib::valuekey($firstName);
return $output;
}
}
tl;dr:
SilverStripe templates cannot handle arrays. I guess the array got automatically converted to an ArrayData object.
if you want to be more explicit, you can write:
return new ArrayData([
'Name' => "FOOBAR"
]);
and then in the template:
$FirstNameOfEvent <!-- this will also cause this error, because SSViwer does not know how to render ArrayData -->
<!-- but you can access object properties, like the Name that we defined: -->
$FirstNameOfEvent.Name <!-- will output FOOBAR -->
Long explanation:
forTemplate is a called by the SSViewer when rendering objects.
Basically, it's the SilverStripe equivalent to __toString(), whenever you are trying to output a object to the browser in a SilverStripe template, SSViewer (the renderer) will call forTemplate on that object.
Let me give an example:
class Foo extends VieableData {
public $message = 'Hello World';
public function forTemplate() {
return "This is a foo object with the message '{$this->message}'";
}
}
class PageController extends ContentController {
public function MyFooObject() {
return new Foo();
}
}
so if in your Page.ss template, you call $MyFooObject it will call the function of the same name and get an object. Because it's an object, SSViewer doesn't know how to render and will call Foo->forTemplate(). Which then will result in the output This is a foo object with the message 'Hello World'
ArrayData does not have a forTemplate method, thus you get the error. There are 2 ways to get around that[1]:
subclass ArrayData and implement a forTemplate method that turns your data into a string (or DBField object) that can be output to the browser
Don't try to render ArrayData in your Template, instead access the data directly (like in the tl;dr above, so $MyArrayData.MyField)[2]
[1]: the same is true for all objects
[2]: accessing object properties directly is always possible, even if you have a forTemplate method. forTemplate is just the default what to do if you don't specify a property.
EDIT:
sorry, I partially misunderstood your question/problem.
All the stuff I said above is still true, and important to understand, but it didn't answer your question.
I thought you are calling $getFirstNameOfEvents in the template, but actually, you are using it in a DropDownField (missed that part).
The thing about the SilverStripe CMS is, it also use the same templates system as the frontend for it's own things. So DropDownField will also use SSViewer to render. So my explanation is still true, it just happens inside DropDownField.ss which is a builtin template file. It does something like this:
<select>
<% loop $Source %>
<option value="$Key">$Value</option>
<% end_loop %>
</select>
$Source here is your array ['past' => 'Verlopen', 'today' => 'Vandaag', 'future' => 'Toekomst', $this->getFirstNameOfEvents()] which is automatically converted into ArrayData objects.
Now, the problem is, it doesn't work the way you think it works:
// the result you want:
['past' => 'Verlopen', 'today' => 'Vandaag', 'future' => 'Toekomst', 'Rick' => 'Rick']
// the result your code produces:
['past' => 'Verlopen', 'today' => 'Vandaag', 'future' => 'Toekomst', 0 => ['Rick' => 'Rick']]
notice how you have an array inside an array. Because getFirstNameOfEvents returns an array.
So what you should actually do:
$source = ['past' => 'Verlopen', 'today' => 'Vandaag', 'future' => 'Toekomst'];
$source = array_merge($source, $this->getFirstNameOfEvents());
$form = FieldList::create([
OptionsetField::create('Eventfilters')
->setTitle('')
->setSource($source)
]);

php: call a method of an object coming from an array

I'm trying to figure out a way to call a method of a specified member (Class A) coming from an array of members in class B.
<?php
class A
{
public function do_something()
{
echo "class A did something";
}
}
class B
{
private $arr = array();
private $current_index = 0;
public function add_new_A()
{
$new_a = new A;
array_push($this->arr, (object) [
$this->current_index => $new_a
]);
$this->current_index++;
}
public function get_an_A_by_index($index)
{
return $this->arr{$index};
}
public function do_something_with_A_member_inside_array($index)
{
self::get_an_A_by_index($index)->do_someting();
}
}
$b = new B;
$b->add_new_a();
$b->add_new_a();
echo print_r($b->get_an_A_by_index(0));
echo "\n";
$b->do_something_with_A_member_inside_array(0); // returns error
// console:
// stdClass Object ( [0] => A Object ( ))
// Uncaught Error: Call to undefined method stdClass::do_something();
?>
To wrap things up, I want to know if my approach is considered bad and/or if there is something I can do to fix the error. Before you disagree with my code, take a look at my php code I'm actually working on. That's all for my question.
Now about why I want to call a method of a member A inside . For my assignment I want a program that does something seperately with the method do_something of class a. So far, the only way to do that is by storing seperate members of A.
I'm not sure if I'm approaching this wrong or not, because I'm coming from Java. So, The first thing I came up with was the approach shown above. When I do this in Java, it works fine. But php is different from Java and I'm still learning how php works since I'm new to it.
Your code isn't far off, you've just got an issue with the way you're building up the collection of A objects.
array_push($this->arr, (object) [
$this->current_index => $new_a
]);
This is creating a data structure that I'm pretty sure isn't what you expect. You'll end up with an array full of stdClass objects, each with a single A member and its own internal index:
Array
(
[0] => stdClass Object
(
[0] => A Object
(
)
)
)
You're then retrieving the stdClass object and trying to run the method on that, hence the Call to undefined method stdClass::do_something... error you're seeing.
Instead, all you need to do is this:
$this->arr[$this->current_index] = $new_a;
The rest of your code is just expecting an array of A objects, nothing nested any deeper.
I've put a full example here: https://3v4l.org/ijvQa. Your existing code had a couple of other typos, which are also fixed. You'll spot them easily enough if you turn on error reporting.

Query returns content from previous functions

I have a Chat.php file containing everything query-related. Then there is a Core.php containing connection to database and basic functions used in Chat.php like "query" and "rows" which processes the "query" into array.
In Chat.php there are two functions, the second one printing content of the first when using print_r($this->rows());. checkForLastMessage() is supposed to check one table to see if there are new messages to be pulled from another table with function getNewMessages().
This is how it looks like:
Core.php
<?php
class Core
{
...
public function query($sql)
{
$this->result = $this->db->query($sql);
}
public function rows()
{
for($x = 1; $x <= $this->db->affected_rows; $x++)
{
$this->rows[] = $this->result->fetch_assoc();
}
return $this->rows;
}
}
Chat.php
<?php
class Chat extends Core
{
public function checkForLatestMessage($chatid, $iam)
{
$userinchat='for'.$iam;
$this->query("SELECT anonchat.$userinchat FROM anonchat WHERE anonchat.chatid=$chatid");
$printarray = Array();
$printaray = '';
foreach( $this->rows() as $id )
{
$printarray[] = $id[$userinchat];
}
if($printarray[0] != '')
{
$this->getNewMessages($chatid, $printarray[0]);
}
}
public function getNewMessages($chatid, $requiredMessages)
{
$this->query("SELECT anonmessage.content, anonmessage.timeposted FROM anonmessage WHERE anonmessage.messageid IN ($requiredMessages) ORDER BY anonmessage.timeposted ASC");
print_r($this->rows());
}
The last print_r contains elements from the previous function. I don't know why that is.
Edit. This is the output:
Array ( [0] => Array ( [for1] => 2,4,6 ) [1] => Array ( [content] =>
Message 2 [timeposted] => 2017-08-04 16:12:34 ) [2] => Array (
[content] => Message 4 [timeposted] => 2017-08-04 16:12:48 ) [3] =>
Array ( [content] => Message 6 [timeposted] => 2017-08-04 16:13:03 ) )
Element [0] of array (the one with "for1") is remaining from previous function.
To answer your question, you're initializing the class property $this->rows in the first method and then the 2nd method is appending to it. You need to reset $this->rows before adding to it.
public function getNewMessages($chatid, $requiredMessages)
{
$this->rows = null;
$this->query("SELECT anonmessage...");
print_r($this->rows());
}
Or better yet, reset the variable in the query() method. That way you don't have to do it each time.
Please don't take offense when I say that this is a bad design.
You're trying to write your own DAL (Data Abstraction Layer). This is a very complicated task and there are already lots of implementations out there. The Core class is going to become massive, complicated, and unwieldy when you try to adapt it to a dozen other classes.
PHP only supports a single inheritance so right off the bat you shot your self in the foot because any class that needs DB interactions will have to extend Core and you won't be able to extend anything else.
Consider keeping things simple for now and let each method handle their own queries and DB interactions. Focus on major concepts like DRY, encapsulation and keeping your classes focused on their responsibility.
Ex. checkForLatestMessage() what is this supposed to do? It sounds like should check for messages and then return a boolean (true|false) but instead it is calling getNewMessages() which outputs some data.
I don't know enough about your application to really suggest something useful but this feels a bit better than the path you're heading down. Notice we're not inheriting from Core so you're free to inherit from something else. The methods are concise and do what they say. You'd probably also save a few lines of code this way and it's easier to read.
<?php
class Chat
{
public function hasNewMessages($chatid, $iam)
{
// Query using PDO properly
return (bool)$hasNewMessages;
}
public function getNewMessages($chatid, $requiredMessages)
{
// Query using PDO properly
// An array of data or objects
return $messages;
}
}
/******** Client Code ***********/
$chat = new Chat();
if ($chat->hasNewMessages()) {
foreach ($chat->getNewMessages($id, $required) as $message) {
// $message
}
}
Just some of my thoughts... good luck.

How can I print out a single field in my multidimensional array?

So I do a print for my object: print_r ($objMailer);
And I get the following below:
mymailer Object
(
[_strRecipient:mymailer:private] =>
[_strBcc:mymailer:private] =>
[_strSubject:mymailer:private] =>
[_strEmail:mymailer:private] =>
[_arrData:mymailer:private] => Array
(
[full_name] => brian
[invitee_name] => test
[email] => test#testing.com
[captcha] => kqd2q9
)
[_arrAttachments:mymailer:private] =>
[_blnCaptcha:mymailer:private] => 1
[_arrErrors:mymailer:private] => Array
(
)
)
I need to echo/print out just the 'full_name' field? How can I go about doing this?
You can not trivially. As the print_r output shows that is within a private member.
You can either provide it from within your (?) mymailer object:
return $this->_arrData['full_name'];
or by using Reflection to make it accessible from the outside:
$refObj = new ReflectionObject($objMailer);
$refProp = $refObj->getProperty('_arrData');
$array = $refProp->getValue($objMailer);
echo $array['full_name'];
If you want to echo the value inside a method of mymailer class, you may use:
echo $this->_arrData['full_name'];
Since it's private you'll need to use a getter
The object you are referencing has an _arrData member variable which has private scope resolution, which means you cannot access it from outside the class. Chances are there is a public accessor which will allow you get the information you are after, but no way to tell unless you introspect the object itself.
I would suggest doing something like:
foreach (get_class_methods($mymailer) as $method) { echo 'M: ' . $method . '<br>'; } exit;
Then you can see the methods available to you, chances are there is a getData() method, with which you can do this:
$mailerData = $mymailer->getData();
var_dump($mailerData['full_name']);
There may even be a method to get the full name, something like this:
var_dump($mymailer->getFullname());

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