some strange question, here is my code:
I have to echo $list, but now it's NULL.
the "class GuestBook" read from a file from server, then I add some new info to array and I want to return a new array (array is $list), now it display that $list is NULL
<?php
class GuestBook {
public function __construct($path)
{
$this->path = $path;
$this->list = $list;
}
public function getData() {
$list = file($this->path);
return $this->list;
}
public function append($text)
{
$list = file($this->path);
$list[] = $text;
var_dump($list);
return $this->list;
}
public function getList()
{
var_dump($list); // NULL
}
};
$newBook = new GuestBook(__DIR__ . '/HW_db.txt');
$newBook->getData();
$newBook->append('HI!');
$newBook->getList(); // NULL??
var_dump($newBook->list); // NULL ??
What is the error here??
Simply put, this will work.
<?php
class GuestBook {
public $path,$list;
public function __construct($path){
$this->path = $path;
}
public function getData() {
if(file_exists($this->path)){
$this->list = file($this->path);
return $this;
}
throw new \Exception(" Your file path is invalid ");
return null;
}
public function append($text){
$this->list[] = $text;
return $this;
}
public function getList(){
return $this->list;
}
}
//usage
$guestbook = new GuestBook(__DIR__ . '/HW_db.txt');
$guestbook->getData()->append("HI");
var_dump($guestbook->getList());
?>
How does it work
Ok, so let's start from the top; first we declare the class name, after which we set the variables. In this case you used $path and $list, for simplicity we set them to public. If you would like to know more about variable scope, I refer to the manual.
Then we move on to the constructor, we set the path we get passed to that public $path variable, so we can use it throughout the class.
The getData() function, we check if the file exists. If it exists, we load the data into the public $list variable. If it doesn't we throw an Exception.
In order to append to that list, we just add to the array of public $list.
Then in order to get the entire list, we return $this->list in the getList() function.
With all that, you have a well functioning class.
You are using local variable, instead of a class property. Correct is:
public function getList()
{
var_dump($this->list);
}
What is wrong in your code?
Simply put: everything.
How to fix it
Let's analyze each piece of it and see how to correct and improve it.
class GuestBook {
public function __construct($path)
{
$this->path = $path;
$this->list = $list;
}
The first statement of the constructor is correct, it does what the constructor should do: it initializes the class members. It would be nice for the readers of the code to also declare $path and $list on top of the class definition like this:
class GuessBook {
private $path;
private $list;
public function __construct($path)
...
The second statement of the constructor, however, initializes the list property of the object ($this->list) with the value of variable $list that is not mentioned anywhere in the function; it doesn't exist and that means it is undefined and its value is NULL.
You can either pass $list as the second argument of the constructor (public function __construct($path, $list)) or initialize $this->list with a fixed value. From the way you use the class I think the second option is the way to go and the most appropriate value for $this->list is an empty array().
public function getData()
{
$list = file($this->path);
return $this->list;
}
The first statement loads the content of a file, splits it into lines and stores the array of lines into the local variable $list. Nothing wrong here. But the second statement returns the value of the list property of the current object that, let's remember, was initialized in the constructor with NULL.
I believe your intention is to store in the list property the array of lines loaded from the file. That is achieved by changing the first line of code to:
$this->list = file($this->path);
While we are here, the name of the method doesn't clearly represent what it does. Its main purpose is to load the content of the file in $this->list. A better name for it is loadData().
public function append($text)
{
$list = file($this->path);
$list[] = $text;
var_dump($list);
return $this->list;
}
First of all, the var_dump() has no place here (or everywhere). It is a debugging function, one puts it into the code, uses the data it displays and removes it from the code as soon as the code was fixed and its presence is not needed any more. I hope you let it there in the code posted here and you remove it from the real code.
This method has the same problem as getData(). It loads the data into the local variable $list that is destroyed as soon as the function completes. However, using $this->list instead of $list fixes only half of the problem.
What happens when you call append() twice in a row (after you have fixed it)? The second call discards the content of $this->list prepared by the first call. This (partially) happens because append() does two things: loads the data and appends a new line. Let getData() load the data and change append() to only append one new line:
public function append($text)
{
$this->list[] = $text;
return $this->list;
}
public function getList()
{
var_dump($list); // NULL
}
The function name (getList()) says it returns a value. Instead it displays something. This is confusing. Again, there is no place for var_dump() here.
But the main problem of this function is that it attempts to use the local variable $list that is not initialized. It should probably read:
public function getList()
{
return $this->list;
}
};
There is no need to put a semicolon (;) after a class definition. It is not an error, though. It is just an empty statement.
$newBook = new GuestBook(__DIR__ . '/HW_db.txt');
$newBook->getData();
$newBook->append('HI!');
$newBook->getList(); // NULL??
Nothing wrong here, just unexpected. A reader that doesn't know the code of class GuestBook expects $newBook->getList() returns a value and not print anything.
var_dump($newBook->list); // NULL ??
$newBook->list is NULL because the one and only time when something was stored in it is in the constructor. And the value put in it by the constructor is the value of an uninitialized variable, i.e. NULL.
More than that, the property list is accessible from outside the class because you didn't declare it and PHP created it as public the first time when it was set. In a correct OOP code, most of the times the properties are private or protected, only (some of) the methods are public.
How to make it work
Let's fix and put everything together:
class GuestBook {
private $path;
private $list;
public function __construct($path)
{
$this->path = $path;
$this->list = array();
}
public function loadData()
{
$this->list = file($this->path);
return $this->list;
}
public function append($text)
{
$this->list[] = $text;
return $this->list;
}
public function getList()
{
return $this->list;
}
}
$newBook = new GuestBook(__DIR__ . '/HW_db.txt');
$newBook->loadData();
$newBook->append('HI!');
print_r($newBook->getList());
While this code works, it still shows several weak points. If you call loadData() again after append(), the previously appended data is discarded and the content of the file is loaded again. If you don't call loadData() at all, the content of the file is ignored.
These flaws can be easily fixed by merging the code of loadData() into the constructor and removing the method loadData() altogether. This way the class goes one step further towards a correct OOP implementation. The users of the class does not and should not care about loading or saving the data; this is an implementation detail and it's the responsibility of the class.
Also, you need a method to put the data back into the file. This can be done in the destructor of the class.
class GuestBook {
private $path;
private $list;
public function __construct($path)
{
$this->path = $path;
$this->list = file($this->path);
}
public function append($text)
{
$this->list[] = $text;
return $this->list;
}
public function getList()
{
return $this->list;
}
public function __destruct()
{
file_put_contents(this->file, implode('', $this->list));
}
}
$newBook = new GuestBook(__DIR__ . '/HW_db.txt');
$newBook->append('HI!');
print_r($newBook->getList());
unset($newBook); // This calls the destructor
The code still has a flaw: file() loads the content of the file, splits it into lines but each line ends with the end-of-line character ("\n"). When the data is saved back into the file, the lines are joined together. If, because of append(), the list contains entries that do not end with "\n" or entries that contain embedded newlines, the next time when the data is loaded from file the content of $this->list will be different.
My suggestion is to always make sure the elements of $this->list do not contain new line characters.
Change the constructor to remove the newlines and the destructor to put them back when it saves the data in the file. Also change append() to replace the newline characters with spaces.
The final code
class GuestBook {
private $path;
private $list;
public function __construct($path)
{
$this->path = $path;
$this->list = explode("\n", file_get_contents($this->path));
}
public function append($text)
{
$this->list[] = trim(str_replace("\n", ' ', $text));
return $this->list;
}
public function getList()
{
return $this->list;
}
public function __destruct()
{
file_put_contents(this->file, implode("\n", $this->list));
}
}
What this code doesn't do and it must do
The code above works fine in an ideal world where all the files exist and are readable and writable and nothing bad ever happens. Since we are living in the real world and not in a fairy tale, bad things sometimes happen and the code must be ready to handle them.
For the sake of simplicity, the code above doesn't handle the errors: file doesn't exist, it cannot be read or written. This can be easily accomplished by using the function file_exists() and by checking the values returned by file_get_contents() and file_put_contents().
Further reading
Read the entire section about PHP classes and objects. It doesn't replace a good introduction into OOP, though. Find and read one if you feel you need (after or while you read the PHP documentation).
The documentation of PHP functions used in the code: file(), file_get_contents(), file_put_contents(), trim(), str_replace(), explode(), implode().
Also read about the scope of variables in PHP.
public function append($text) {
$list = file($this - > path);
$list[] = $text;
var_dump($list);
return $this - > getlist($list); // GET LIST
}
public function getList($list) {
var_dump($list); // NULL
}
Related
In PHP using method chaining how would one go about supplying a functional call after the last method being called in the chain?
Also while using the same instance (see below). This would kill the idea of implementing a destructor.
The end result is a return value and functional call of private "insert()" from the defined chain properties (of course) without having to call it publicly, no matter of the order.
Note, if I echo (__toString) the methods together it would retrieve the final generated unique code which is normal behavior of casting a string.
Example below:
class object
{
private $data;
function __construct($name) {
// ... some other code stuff
}
private function fc($num) {
// some wicked code here
}
public function green($num) {
$this->data .= fc($num*10);
return $this;
}
public function red($num) {
$this->data .= fc($num*25);
return $this;
}
public function blue($num) {
$this->data .= fc($num*1);
return $this;
}
// how to get this baby to fire ?
private function insert() {
// inserting
file_put_content('test_code.txt', $this->data);
}
}
$tss = new object('index_elements');
$tss->blue(100)->green(200)->red(100); // chain 1
$tss->green(0)->red(100)->blue(0); // chain 2
$tss->blue(10)->red(80)->blue(10)->green(0); // chain 3
Chain 1, 2, and 3 would generated an unique code given all the values from the methods and supply an action, e.g. automatically inserting in DB or creating a file (used in this example).
As you can see no string setting or casting or echoing is taking place.
You could keep a list of things that needs to be initialised and whether they
have been so in this instance or not. Then check the list each time you use
one of the initialisation methods. Something like:
class O {
private $init = array
( 'red' => false
, 'green' => false
, 'blue' => false
);
private function isInit() {
$fin = true;
foreach($this->init as $in) {
$fin = $fin && $in;
}
return $fin;
}
public function green($n) {
$this->init['green'] = true;
if($this->isInit()) {
$this->insert();
}
}
public function red($n) {
$this->init['red'] = true;
if($this->isInit()) {
$this->insert();
}
}
public function blue($n) {
$this->init['blue'] = true;
if($this->isInit()) {
$this->insert();
}
}
private function insert() {
echo "whee\n";
}
}
But personally I think this would be more hassle then it's worth. Better imo
to expose your insert method and let the user of you code tell when the
initialisation is finished. So something that should be used like:
$o->red(1)->green(2)->blue(0)->insert();
-update-
If it's the case that it's impossible to predict what functions need to be called
you really do need to be explicit about it. I can't see a way around that. The reason
is that php really can't tell the difference between
$o1 = new A();
$o2 = $o1->stuff();
and
$o2 = (new A())->stuff();
In a language that allows overloading = I guess it would be possible but really
really confusing and generally not a good idea.
It is possible to move the explicit part so that it's not at the end of the call
chain, but I'm not sure if that would make you happier? It would also go against
your desire to not use another instance. It could look something like this:
class O {
public function __construct(InitO $ini) {
// Do stuff
echo "Whee\n";
}
}
class InitO {
public function red($n) {
return $this;
}
public function green($n) {
return $this;
}
public function blue($n) {
return $this;
}
}
$o = new O((new InitO())->red(10)->red(9)->green(7));
You can of course use just one instance by using some other way of wrapping
but the only ways I can think of right now would look a lot uglier.
Im with PeeHaa, this makes no sense! :)
Only chance to have something magically happen after the last chain was used (without being able to look into the future) is a Destructor/Shutdown function OR a manually cast/call to insert()
You can also decide to implement this statically without using objects.
<?php
class Object
{
private static $data;
public static function set($name)
{
// ... some other code stuff
}
private static function fc($num)
{
// some wicked code here
}
public static function green($num)
{
self::$data .= self::fc($num*10);
return new static;
}
public static function red($num)
{
self::$data .= self::fc($num*25);
return new static;
}
public static function blue($num) {
self::$data .= self::fc($num*1);
return new static;
}
// how to get this baby to fire ?
public static function insert()
{
// inserting
file_put_content('test_code.txt', self::$data);
}
}
//$tss = new object('index_elements');
$Object::set('index_elements')->blue(100)->green(200)->red(100)->insert(); // chain 1
$Object::set('index_elements')->green(0)->red(100)->blue(0)->insert(); // chain 2
$Object::set('index_elements')->blue(10)->red(80)->blue(10)->green(0)->insert(); // chain 3
?>
Ok let's see a code example
<?php
// map dummy class
class map
{
// __call magic method
public function __call($name, $args)
{
return $this;
}
}
// now we chain
$map = new map;
// let's find me
$map->start('here')
->go('right')
->then()
->turn('left')
->and('get water')
->dontEat()
->keep('going')
->youShouldSeeMe('smiling');
here we don't know what the last method would be and we need to trigger a kinda operation or event once we hit the end.
According to data structure we can call this the LIFO stack. (Last in first out)
so how did i solve this on PHP?
// i did some back tracing
... back to the __call function
function __call($name, $args)
{
$trace = debug_backtrace()[0];
$line = $trace['line'];
$file = $trace['file'];
$trace = null;
$getFile = file($file);
$file = null;
$getLine = trim($getFile[$line-1]);
$line = null;
$getFile = null;
$split = preg_split("/(->)($name)/", $getLine);
$getLine = null;
if (!preg_match('/[)](->)(\S)/', $split[1]) && preg_match('/[;]$/', $split[1]))
{
// last method called.
var_dump($name); // outputs: youShouldSeeMe
}
$split = null;
return $this;
}
And whoolla we can call anything once we hit the bottom.
*(Notice i use null once i am done with a variable, i come from C family where we manage memory ourselves)
Hope it helps you one way or the other.
So there are two things that I am stuck on now. First
class DisplayTaxonomy {
public $MyArray[]= new DisplayTaxonomy(); //the compiler says syntax error
//things I have tried
//public $ss[]= new Object();
}
Second! in a function like this:
public function whatever()
{
$Temp = new DisplayTaxonomy();
$Temp->setADTitle("1");
$MyArray[]= $Temp;//works just fine.
//then i tried to return the array
return $MyArray[];
}
I get the following
//Cannot use [] for reading in C:\xampp\htdocs\wordpress\wp-//content\themes\twentyeleven\page.php on line 52
then in the client side
$y=new DisplayTaxonomy();
$myArray[]=new DisplayTaxonomy();//works fine dont know why I cant do this in theclass.
$myArray[]=$y->getArrayOfDisplayTaxonomyObjects();
echo $myArray[0]->getADTitle();
It seems you want to create a class that handles a collection of Taxonomy objects. In that case you should have two classes, instead of making a class store instances of itself.
class TaxonomyContainer
{
private $collection = array();
public function addElement(DisplayTaxonomy $element)
{
$this->collection[] = $element;
}
public function getElements()
{
return $this->collection;
}
}
class DisplayTaxonomy
{
private $adTitle;
public function setAdTitle($adTitle)
{
$this->adTitle = $adTitle;
}
//and other functionality the Taxonomy object should have
}
Then you can avoid the ugly self replicating behaviour and separate your concerns.
$container = new TaxonomyContainer();
$element = new DisplayTaxonomy();
$container->addElement($element);
On the next level, it might be worth considering the use of one of PHP's predefined interfaces for the Container class.
You declare objects in the function body and initiate them in the constructor (or a member function). You don't use [] when returning an array, $array[] has the same functionality as array_push, nothing more.
To clarify,
class myClass {
public $obj = array();
public function __construct() {
$this->obj[] = new OtherObject();
}
public function getObj() {
return $this->obj;
}
}
You cannot do this :
class DisplayTaxonomy {
public $MyArray[]= new DisplayTaxonomy();
}
because it's like an infinite loop :) So you have to use __contruct() function.
After change the :
return $MyArray[];
to :
return $MyArray;
Your first issue is due to trying to call the class you're declaring.
class DisplayTaxonomy {
public $MyArray[]= new DisplayTaxonomy();
You should initialize your object outside of the class, in the portion of code that you need to reference the class.
In addition, the object is already an array so you can omit attaching [] to the end return $MyArray:
public function whatever(){
$Temp = new DisplayTaxonomy();
$Temp->setADTitle("1");
$MyArray[] = $Temp;
return $MyArray;
}
You're declaring the array object here:
$MyArray[]= $Temp;//works just fine
You can't call code (new DisplayTaxonomy()) when definining class properties. You'll have to assign the value in the constructor of the class:
class Foo
{
public $MyArray;
public function __construct()
{
$this->MyArray = new DisplayTaxonomy();
}
}
The other issue is that the $array[] shortcut is for appending to an array. If you want to just return the array (and not write to the array which you're asking about with []), simply skip []:
return $MyArray;
Expanded:
As Vincent Joigƞie pointed out below; you're trying to create a class with the same name as the class you're already creating. This doesn't make any sense, except for static properties. In that case you can't use __construct(), but would rather create / set the object in the static method you're calling to retrieve the object the first time:
static public function getInstance()
{
if (self::$MyArray === null)
{
self::$MyArray = new DisplayTaxonomy();
}
return self::$MyArray;
}
This is however probably not what you want, and it seems you've confused something in your logic in your class definition. Guessing freely you might just want:
class Foo
{
public $MyArray = array();
}
As array() is a static assignment (and not a function call), it's allowed in the class definition.
I can't quite understand why the output of this code is '1'.
My guess is that php is not behaving like most other OO languages that I'm used to, in that the arrays that php uses must not be objects. Changing the array that is returned by the class does not change the array within the class. How would I get the class to return an array which I can edit (and has the same address as the one within the class)?
<?php
class Test
{
public $arr;
public function __construct()
{
$this->arr = array();
}
public function addToArr($i)
{
$this->arr[] = $i;
}
public function getArr()
{
return $this->arr;
}
}
$t = new Test();
$data = 5;
$t->addToArr($data);
$tobj_arr = $t->getArr();
unset($tobj_arr[0]);
$tobj_arr_fresh = $t->getArr();
echo count($tobj_arr_fresh);
?>
EDIT: I expected the output to be 0
You have to return the array by reference. That way, php returns a reference to the array, in stead of a copy.
<?php
class Test
{
public $arr;
public function __construct()
{
$this->arr = array();
}
public function addToArr($i)
{
$this->arr[] = $i;
}
public function & getArr() //Returning by reference here
{
return $this->arr;
}
}
$t = new Test();
$data = 5;
$t->addToArr($data);
$tobj_arr = &$t->getArr(); //Reference binding here
unset($tobj_arr[0]);
$tobj_arr_fresh = $t->getArr();
echo count($tobj_arr_fresh);
?>
This returns 0.
From the returning references subpage:
Unlike parameter passing, here you have to use & in both places - to
indicate that you want to return by reference, not a copy, and to
indicate that reference binding, rather than usual assignment, should
be done
Note that although this gets the job done, question is if it is a good practice. By changing class members outside of the class itself, it can become very difficult to track the application.
Because array are passed by "copy on write" by default, getArr() should return by reference:
public function &getArr()
{
return $this->arr;
}
[snip]
$tobj_arr = &$t->getArr();
For arrays that are object, use ArrayObject. Extending ArrayObject is probably better in your case.
When you unset($tobj_arr[0]); you are passing the return value of the function call, and not the actual property of the object.
When you call the function again, you get a fresh copy of the object's property which has yet to be modified since you added 5 to it.
Since the property itself is public, try changing:
unset($tobj_arr[0]);
To: unset($t->arr[0]);
And see if that gives you the result you are looking for.
You are getting "1" because you are asking PHP how many elements are in the array by using count. Remove count and use print_r($tobj_arr_fresh)
I know you can assign a function's return value to a variable and use it, like this:
function standardModel()
{
return "Higgs Boson";
}
$nextBigThing = standardModel();
echo $nextBigThing;
So someone please tell me why the following doesn't work? Or is it just not implemented yet? Am I missing something?
class standardModel
{
private function nextBigThing()
{
return "Higgs Boson";
}
public $nextBigThing = $this->nextBigThing();
}
$standardModel = new standardModel;
echo $standardModel->nextBigThing; // get var, not the function directly
I know I could do this:
class standardModel
{
// Public instead of private
public function nextBigThing()
{
return "Higgs Boson";
}
}
$standardModel = new standardModel;
echo $standardModel->nextBigThing(); // Call to the function itself
But in my project's case, all of the information stored in the class are predefined public vars, except one of them, which needs to compute the value at runtime.
I want it consistent so I nor any other developer using this project has to remember that one value has to be function call rather then a var call.
But don't worry about my project, I'm mainly just wondering why the inconsistency within PHP's interpreter?
Obviously, the examples are made up to simplify things. Please don't question "why" I need to put said function in the class. I don't need a lesson on proper OOP and this is just a proof of concept. Thanks!
public $nextBigThing = $this->nextBigThing();
You can only initialize class members with constant values. I.e. you can't use functions or any sort of expression at this point. Furthermore, the class isn't even fully loaded at this point, so even if it was allowed you probably couldn't call its own functions on itself while it's still being constructed.
Do this:
class standardModel {
public $nextBigThing = null;
public function __construct() {
$this->nextBigThing = $this->nextBigThing();
}
private function nextBigThing() {
return "Higgs Boson";
}
}
You can't assign default values to properties like that unless that value is of a constant data type (such as string, int...etc). Anything that essentially processes code (such as a function, even $_SESSION values) can't be assigned as a default value to a property. What you can do though is assign the property whatever value you want inside of a constructor.
class test {
private $test_priv_prop;
public function __construct(){
$this->test_priv_prop = $this->test_method();
}
public function test_method(){
return "some value";
}
}
class standardModel
{
// Public instead of private
public function nextBigThing()
{
return "Higgs Boson";
}
}
$standardModel = new standardModel(); // corection
echo $standardModel->nextBigThing();
EDIT: Language is PHP.
So, I'm implementing a listener/observer pattern. One of the objects I implemented was an object for including files. Which works in a way like this:
class IncludeEvent extends Event {
protected $File = '';
public $Contents;
....
public function Begin() {
....
// this is the ONLY time $Contents is modified:
$this->Contents = ob_get_contents();
ob_end_clean();
....
}
....
if ($this->Notify(new IncludeObject($this->File,'OBOutput', $this)) !== false) {
print $this->Contents;
}
}
Now, whenever an object wants to signal its listeners that an event happened, they need to use the Notify function.
So at one point I had to send a reference to the object to its listeners, so they could have access to the included content ($Contents).
So I used Notify() passing $this as the parameter. When I try to access the $Content from the other object, it simply returns as an empty string.
Note: The object functions as it should in a normal context, only when I pass it as a function parameter I can't access its $Content.
The constructor for IncludeObject():
public function __construct($F= '', $M= -1, &$IV = null) {
$this->File = $F;
$this->Func = $M;
$this->IncEv = $IV;
}
The listener accesses the IncludeEvent object like this:
public function OBOutput($IncObj){
print $IncObj->IncEv->Contents;
return false;
}
So yeah, how do I access long strings from object references? It works fine with short strings, but something like a long included file just returns empty.
Notify() is defined as:
final public function Notify($EvObj = null) {
foreach ($this->listen_list as $z) {
$d = call_user_func(array($z, $EvObj->Func), $EvObj);
if ($d == true || is_null($d)) {
return true;
}
}
return false;
}
how do I access long strings from object references? It works fine with short strings, but something like a long included file just returns empty.
you're barking up the wrong tree. and btw, you don't need that & at all. objects are passed by reference without it (not in PHP 4, but you shouldn't be using that nowadays).