Im pretty new to OOP PHP, and so I'm trying to learn.
I have a class called "Awesome_Car" that i define like so
class Awesome_Car {
public $attributes;
public $name;
function __construct($name, $atts) {
$this->name = $name;
$this->attributes = $atts;
}
} // end of class
And I instantiate this class x times somewhere in the code:
$car1 = new Awesome_Car('ford', array( 'color'=>'blue', 'seats' => 6 ));
$car2 = new Awesome_Car('bmw', array( 'color'=>'green', 'seats' => 5 ));
Now I would like to make a normal function that allows me to get - and manipulate - a specific instance of that class by name. Something like
function get_specific_car_instance($name) {
//some code that retrives a specific instance by name
//do stuff with instance
//return instance
}
I have seen people storing each instance in a global variable as an array of object, but I've also read that global variables are considered bad practice? And I do find them a bit annoying to work with as well.
What would be a better way of doing this? preferably an OOP approach?
If you are creating the instances dynamically, then storing them in an array is the generally accepted way. It doesn't have to be global however.
$cars = array();
$cars['ford'] = new Awesome_Car('ford', array( 'color'=>'blue', 'seats' => 6 ));
$cars['bmw'] = new Awesome_Car('bmw', array( 'color'=>'green', 'seats' => 5 ));
$ford = $cars['ford'];
This ofcourse can be abstracted by a function such as:
function get_car(&$cars, $name) {
if (! isset($cars[$name])) {
throw new \InvalidArgumentException('Car not found');
}
return $cars[$name];
}
$ford = get_car($cars, 'ford');
Or with more advanced container classes such as:
// Requires doctrine/common
use Doctrine\Common\Collections\ArrayCollection;
$cars = new ArrayCollection();
$cars->set('ford', new Awesome_Car('ford', array( 'color'=>'blue', 'seats' => 6 )));
$cars->set('bmw', new Awesome_Car('bmw', array( 'color'=>'green', 'seats' => 5 )));
$ford = $cars->get('ford');
How you store them for later use depends quite a bit on how you are dynamically creating them though.
You can create your own repository; a class whose only purpose is to create, track, and recover these cars. That'll allow you to avoid using a global variable. Of course, you'll need a way to access the repository then. You could always make it Static, but then you're basically back to a global in a way.
class CarRepository {
private $cars = array();
public function makeCar( $name, $atts ) {
$this->cars[] = new Awesome_Car($name, $atts);
}
public function findByName($name) {
foreach( $this->cars as $car ) {
if( $car->name == $name ) {
return $car;
}
}
}
}
// you'll need a way to obtain the repository to find cars; but it means you can have different repositories in your code
$repo = new CarRepository;
$repo->makeCar( 'ford', array( 'color'=>'blue', 'seats' => 6 ) );
$repo->findByName( 'ford' );
Or a fully static version:
class CarRepository {
private static $cars = array();
public static function makeCar( $name, $atts ) {
self::$cars[] = new Awesome_Car($name, $atts);
}
public static function findByName($name) {
foreach( self::$cars as $car ) {
if( $car->name == $name ) {
return $car;
}
}
}
}
// you can access this from ANYWHERE, but you can only ever have a single repository
CarRepository::makeCar( 'ford', array( 'color'=>'blue', 'seats' => 6 ) );
CarRepository::findByName( 'ford' );
Related
i have a object like this:
CORE::$ObjClassInstABS['ABS']['DATA']
that contains an array of Class->Method:
array (
'DATA' =>
array (
'USERDATAMANAGER' =>
Class_UserdataManager::__set_state(array(
)),
'PRODDATAMANAGER' =>
Class_ProddataManager::__set_state(array(
)),
),
)
i create a new object, of type class Like this:
CORE::$ObjClassInstABS['ABS']['ABSDATAMANAGER'] = new class;
i cant but need pass all the methods of the first object, ignoring the class of origin to the class i create on fly, and that allows me to execute the functions from the class declared on the fly.
does this exist in php 7.0 or is there any way to achieve this reach??
It would be like cloning the methods of several classes to a single and new class.
Answer for #Damian Dziaduch comments
the piece of code that i used to Dynamically Instance all class file from a directory is this, and populate the first object with instance of class:
CORE::$ObjClassInstABS['ABS']['ABSDATAMANAGER']= new class;
foreach (CORE::$ObjClassABS['DATA'] as $key => $name) {
if (strpos($name, 'class.') !== false) {
$name = basename($name);
$name = preg_replace('#\.php#', '', $name);
$names = explode(".", $name);
foreach ($names as $key => $namesr) {
$names[$key] = ucfirst(strtolower($namesr));
}
$name = implode('_', $names);
$NamesClass = $name . 'Manager';
$InstanceClass = strtoupper(preg_replace('#\Class_#', '', $NamesClass));
CORE::$ObjClassInstABS['ABS']['DATA'][$InstanceClass] = $this->$InstanceClass = new $NamesClass();
}
}
the result of it is the Array printed at start of the post CORE::$ObjClassInstABS['ABS']['DATA'] .
if you see at start of foreach i have the new class declaration to use, in loop, how can i populate CORE::$ObjClassInstABS['ABS']['ABSDATAMANAGER'] in the loop, it with all methods of the first object instance, and make it executables?
that i whant (not work):
foreach ( CORE::$ObjClassInstABS['ABS']['DATA'] as $key => $value ) {
CORE::$ObjClassInstABS['ABS']['ABSDATAMANAGER'] .= Clone($value);
}
$value represent where is storing the methods:
::__set_state(array()),
As requested.
Not sure whether this will fill you requirements... The question is whether you are able to overwrite the CORE::$ObjClassInstABS
<?php
CORE::$ObjClassInstABS = new class extends \ArrayIterator {
private $container = [];
public function __construct(array $container)
{
$this->container = [
'ABS' => [
'DATA' => [
'USERDATAMANAGER' => new class {},
'PRODDATAMANAGER' => new class {},
],
],
];
}
public function offsetExists($offset)
{
return isset($this->container[$offset]);
}
public function offsetGet($offset)
{
return isset($this->container[$offset]) ? $this->container[$offset] : null;
}
public function offsetSet($offset, $value)
{
if (is_null($offset)) {
$this->container[] = $value;
} else {
$this->container[$offset] = $value;
}
}
public function offsetUnset($offset)
{
unset($this->container[$offset]);
}
};
I wonder if it's possible to achieve similar functionality to C#'s compact instantiation syntax:
itemView.Question = new ItemViewQuestion()
{
AnswersJSON = itemView.Answer.ToJSONString(),
Modified = DateTime.Now,
ModifiedBy = User.Identity.Name
};
I wish to be able to create an object of arbitrary class passing their properties without having to set up constructor code for these properties.
To put up another example, this can be done with stdClass like this:
(object) ["name" => "X", "age" => 30]
Type juggling does not work for custom classes, however.
There is no such functionality natively in PHP, unfortunately.
But you can create a class in your project and extend it in the classes you wish to instantiate without a constructor. Something like this:
<?php
class Fillable{
public static function fill($props)
{
$cls = new static;
foreach($props as $key=>$value){
if (property_exists(static::class,$key)){
$cls->$key = $value;
}
}
return $cls;
}
}
class Vegetable extends Fillable
{
public $edible;
public $color;
}
$veg = Vegetable::fill([
'edible' => true,
'color' => 'green',
'name' => 'potato' //Will not get set as it's not a property of Vegetable. (you could also throw an error/warning here)
]);
var_dump($veg);
Checkout this fiddle for the working example
This is valid in PHP though:
<?php
class Demo {
public function getA() {
return $this->Options['A'];
}
}
$D = new Demo();
$D->Options = Array(
'A' => '1',
'B' => '2',
'C' => '3'
);
var_dump($D->getA());
Or, something like this:
<?php
class Demo {
public function __construct($Options) {
$this->Options = $Options;
}
public function getA() {
return $this->Options['A'];
}
}
$D = new Demo(Array(
'A' => '1',
'B' => '2',
'C' => '3'
));
var_dump($D->getA());
Or even this:
<?php
class Demo {
public function __construct($Options) {
foreach ($Options as $key=>$value) $this->$key = $value;
}
public function getA() {
return $this->A;
}
}
$D = new Demo(Array(
'A' => '1',
'B' => '2',
'C' => '3'
));
var_dump($D->getA());
I guess it really depends what are you trying to achieve? You said you do not want to use magic functions or setters, but is there more to it?
Obviously php doesn't have this. Somewhere a function is required. I did an implementation using a trait which is close.
<?php
Trait Init {
public function init($arr) {
$vars = get_object_vars($this);
foreach($arr as $k => $v) {
if ( array_key_exists($k, $vars) ) $this->$k = $v;
}
}
}
class Demo {
use Init;
public $answersJSON;
public $modified;
public $modifiedBy;
}
$obj = new Demo();
$obj->init(['modified' => 'now']);
print_r($obj);
I know serialization is the process of taking a complicated object and reducing it down to zeros and ones for either storage or transmission. But I'm confused about the interface Serializable
For example, we have colde like
class Artist implements Serializable {
public function serialize() {
return serialize(
array("earliest" =>self::$earliestDate,
"first" => $this->firstName,
"last" => $this->lastName,
"bdate" => $this->birthDate,
"ddate" => $this->deathDate,
"bcity" => $this->birthCity,
"works" => $this->artworks
);
);
}
public function unserialize($data) {
$data = unserialize($data);
self::$earliestDate = $data['earliest'];
$this->firstName = $data['first'];
$this->lastName = $data['last'];
$this->birthDate = $data['bdate'];
$this->deathDate = $data['ddate'];
$this->birthCity = $data['bcity'];
$this->artworks = $data['works'];
}
//...
}
For example, unserialize($data)is the method that we implements, but how come it call itself inside the function like "$data = unserialize($data)",isn't it a infinite loop? Is the inside function unserialize($data) the same one as outside unserialize($data) function?
I using zf2's tableGateway and I'm unsure of the design it leads to.
Here is the canonical example of how to use zf2's tableGateway to do an insert (this from the docs):
public function saveAlbum(Album $album)
{
$data = array(
'artist' => $album->artist,
'title' => $album->title,
);
$id = (int)$album->id;
if ($id == 0) {
$this->tableGateway->insert($data);
} else {
if ($this->getAlbum($id)) {
$this->tableGateway->update($data, array('id' => $id));
} else {
throw new \Exception('Form id does not exist');
}
}
}
But defining the $data array seems redundant because I already have an Album class that looks like this:
class Album
{
public $id;
public $artist;
public $title;
public function exchangeArray($data)
{
$this->id = (isset($data['id'])) ? $data['id'] : null;
$this->artist = (isset($data['artist'])) ? $data['artist'] : null;
$this->title = (isset($data['title'])) ? $data['title'] : null;
}
}
In my own project I have a model with about 25 properties (a table with 25 columns). It seems redundant to have to define the class with 25 properties and than also write a $data array inside of the method of a class implementing tableGateway with an element for every one of those properites. Am I missing something?
Another way is to use RowGateway http://framework.zend.com/manual/2.3/en/modules/zend.db.row-gateway.html
Briefly, I'd extend album class from \Zend\Db\RowGateway\AbstractRowGateway class.
<?php
namespace Module\Model;
use Zend\Db\RowGateway\AbstractRowGateway;
use Zend\Db\Adapter\Adapter;
use Zend\Db\Sql\Sql;
class Album extends AbstractRowGateway
{
protected $primaryKeyColumn = array( 'id' );
protected $table = 'album';
public function __construct( Adapter $adapter )
{
$this->sql = new Sql( $adapter, $this->table );
$this->initialize();
}
}
And then you can do like this
$album->title = "Some title";
$album->save();
Or
$album->populate( $dataArray )->save();
You may want to take a look at my QuickStart 101 Tutorial.
Basically you could do:
saveAlbum(Album $albumObject)
{
$hydrator = new ClassMethods(false);
$albumArray = $hydrator->extract($albumObject);
// v-- not too sure if that one-liner works; normal if() in case it doesn't
isset($albumArray['id']) ? unset($albumArray['id']) :;
// insert into tablegateway
}
I have got this little snippet of code, I want to be able to define each array element as a new data member.
class Core_User
{
protected $data_members = array(
'id' => '%d',
'email' => '"%s"',
'password' => '"%s"',
'title' => '"%s"',
'first_name' => '"%s"',
'last_name' => '"%s"',
'time_added' => '%d' ,
'time_modified' => '%d' ,
);
function __construct($id = 0, $data = NULL)
{
foreach($this->data_members as $member){
//protected new data member
}
}
//protected new data member
You won't be able to create a non-public property at runtime. If protected is paramount, you can declare a protected array or object and insert key/values into it in the constructor
Always use $this when you want to access object's members (it should be $this->data_members in constructor).
You can try defining magic methods __get & __set (I'm not sure if they can be protected though). :
protected function __get($name){
if (array_key_exists($name,$this->data_memebers))
{
return $this->data_memebers[$name];
}
throw new Exception("key $name doesn't not exist");
}
protected function __set($name,$value){
if (array_key_exists($name,$this->data_memebers))
{
$this->data_memebers[$name] = $value;
}
throw new Exception("key $name doesn't not exist");
}
What you want to achieve is possible, however you won't be able to make the new properties protected (as this is only possible for predefined members).
function __construct($id = 0, $data = NULL)
{
foreach($this->$data_memebers as $name => $value ){
$this->$name = $value;
}
}
Note the use of the $ before name in $this->$name: This makes PHP use the current value of the $name variable as property.