PHP OOP practice - php

I think code given below works fine (I am busy on learning OOP PHP and not tested these code yet) if I want to retrieve single record. What if I want to loop the record ? How to do that ? Can I use single class to retrieve single and loop record ? If yes how ?
include('class.database.php');
class News
{
protected $id;
protected $title;
protected $detail;
protected $updatedon;
protected $views;
protected $pic;
protected $cat;
protected $reporter;
function __construct ($id);
$newsdb = new Database;
$Query = "SELECT * FROM news WHERE nws_sn =".$id;
$db->query($Query);
$db->singleRecord();
$this->id = $newsdb->Record['nws_sn'];
$this->title = $newsdb->Record['nws_title'];
$this->detail = $newsdb->Record['nws_detail'];
$this->updatedon = $newsdb->Record['nws_time'];
$this->views = $newsdb->Record['nws_view'];
$this->pic = $newsdb->Record['nws_pic'];
$this->cat = $newsdb->Record['nws_cat_id'];
$this->reporter = $newsdb->Record['nws_rptr_id']
}
function getId () {
return $this->id;
}
function getTitle () {
return $this->title;
}
function getDetail () {
return $this->detail;
}
function getViews () {
return $this->views;
}
function getImage () {
return $this->pic;
}
function getTime () {
return $this->updatedon;
}
}

You want to use a constructor to initialize an internal state of your object. In your case you do too much in your constructor which also breaks "single responsibility principle". It seems that "News" is just an entity or data transfer object, so you have to initialize it from outside.
First, I would keep News just to store information received from database.
Second, I would create a static factory method inside News class so it create an actual News object and populate it with data passed to the method from outside. Alternatively, you could create a factory object to create your entity, but since the construction logic is simple enough, I thought it makes sense to keep it inside a single method.
Consider the code below:
class News
{
protected $id;
protected $title;
protected $detail;
protected $updatedon;
protected $views;
protected $pic;
protected $cat;
protected $reporter;
public static createFromRecord($record)
{
$obj = new self();
$obj->setId($record->Record['nws_sn']);
$obj->setTitle($record->Record['nws_title']);
$obj->setDetail($record->Record['nws_detail']);
$obj->setUpdateon($record->Record['nws_time']);
$obj->setViews($record->Record['nws_view']);
$obj->setPic($record->Record['nws_pic']);
$obj->setCat($record->Record['nws_cat_id']);
$obj->setReporter($record->Record['nws_rptr_id']);
return $obj;
}
function getId () {
return $this->id;
}
function getTitle () {
return $this->title;
}
function getDetail () {
return $this->detail;
}
function getViews () {
return $this->views;
}
function getImage () {
return $this->pic;
}
function getTime () {
return $this->updatedon;
}
// ... add public setters for the properties
}
...
$newsdb = new Database;
$Query = "SELECT * FROM news WHERE nws_sn =".$id;
$db->query($Query);
$record = $db->singleRecord();
$newsObject = News::createFromRecord($record);

Related

Php closure in a class method call

Lets say I have two classes Car and Owner
owner.php =>
class Owner {
public $name;
public function setName($name) {
$this->name = $name;
}
}
and respectively the car.php =>
class Car {
public $owner;
public function setOwner(Owner $owner) {
$this->owner = $owner;
}
}
To set and call methods I normally use this approach =>
$owner = new Owner;
$owner->setName('sam');
$car = new Car;
$car->setOwner($owner);
But what if I want it to do using Closure like below, how do I change the setOwner method accordingly?
$car = new Car;
$car->setOwner(function(Owner $owner) {
$owner->setName('sam');
});
What I want to do is something similar to Laravel where
User::where('car_id', $carId)
or
User::where(function($query) {
//code here
})
Car::setOwner() needs to call its argument with an Owner object.
class Car {
public function setOwner(Callable $ownerSetter) {
$o = new Owner;
$ownerSetter($o);
}
}
But this is a strange way to use a closure. A better example might be:
class Car {
private $owner;
public function setOwner($owner) {
$this->owner = $owner;
}
public function doSomethingToOwner(Callable $something) {
$something($this->owner);
}
}
$car->setOwner($owner);
$car->doSomethingToOwner(function($owner) {
echo $owner->name;
});

Prevent Object recursion/loop by reference in PHP

Haven't found an answer yet but I'm sure there must be one: how do I prevent an object recursion/loop when objects reference each other? An example:
class Patient {
private $Issues = array();
[...]
public function __construct($id) {
[ Get data from DB ]
while ($row = $result->fetch_assoc()) {
$this->Issues[$row['idIssue']] = new Issue($row['idIssue']);
}
[...]
}
}
class Issue {
private $Patient;
[...]
public function __construct($id) {
[ Get data from DB ]
$this->Patient = new Patient($row['idPatient']); <-- Leads to recursion as the patient will load all it's Issues() etc. etc.
[...]
}
}
How do I prevent this? I could use the id of the Patient() instead of the real object but that feels like a hack. Is there a way to use the real object?
Do not recreate object. Just pass the instance of the master object to the detail constructor. E.g.:
class Patient {
private $Issues = array();
[...]
public function __construct($id) {
[ Get data from DB ]
while ($row = $result->fetch_assoc()) {
$this->Issues[$row['idIssue']] = new Issue($row['idIssue'], $this);
}
[...]
}
}
class Issue {
private $Patient;
[...]
public function __construct($id, Patient $patient) {
[ Get data from DB ]
$this->Patient = $patient
[...]
}
}
You can (should !) separate the DB connection/queries from the entities definitions and pass references to relations, otherwise, you can't mock entities, plus mixing DB connection and entities definition goes against the separation of concerns :
// somewhere in your code
$idPatient = 42;
$patient = new Patient();
$patient->setId($idPatient);
// get datas from DB
while ($row = $result->fetch_assoc())
{
$issue = new Issue();
$issue->setId($row['idIssue'])
->setPatient($patient);
$patient->addIssue($issue);
// or, shorter way :
// $patient->addIssues((new Issue())->setId($row['idIssue'])
// ->setPatient($patient));
}
class Patient {
private $Issues = array();
private $Id;
public function addIssue(Issue $issue): self
{
$this->Issues[] = $issue;
return $this;
}
public function setId(int $id): self
{
$this->Id = $id;
return $this;
}
}
class Issue {
private $Patient;
private $Id;
public function addPatient(Patient $patient): self
{
$this->Patient = $patient;
return $this;
}
public function setId(int $id): self
{
$this->Id = $id;
return $this;
}
}

How to load data with new self construction php

I can not load data to properties using this construction I receive null in dump
<?php
namespace App\Domain\Good;
class GoodDto
{
public $name;
public $articul;
public $price;
public $type;
public $qnt;
public $discount;
public $category;
public $description;
public $description2;
public $color;
public function load($data)
{
$this->name = $data['name'];
$this->articul = $data['artikul'];
$this->price = $data['price'];
$this->type = (isset($data['type'])) ? $data['type'] : null;
$this->qnt = $data['count'];
$this->discount = $data['spinner-decimal'];
$this->category = $data['id_cat'];
$this->description = $data['editor1'];
$this->description2 = '';
$this->color = $data['color'];
//$this->user_id = Auth::user()->id;
}
public static function fromRequest($request)
{
dump('inp=>',(new self ())->load($request->input()));
return (new self ())->load($request->input());
}
}
Please explain to me why I receive null while request->input() is an array, I call it from another place
$dto=GoodDto::fromRequest($request);
Method chaining, returns the last return from the chain. The other returns are used to call the next link in the chain.
(new self ())->load()
So load() needs to return $this
public function load($data)
{
...
return $this;
}
Currently it returns null, which is why it returns null.
See you are not saving the instance from the constructor, instead you pass it to load by enclosing it within the (....). By pass it I mean you call the load method on the return from the constructor.
You can test this like so:
class foo{
function load(){
return $this;//return this
}
}
var_dump((new foo)->load());
class bar{
function load(){
//return null
}
}
var_dump((new bar)->load());
Output
//return this
object(foo)#1 (0) {
}
//return null
NULL
sandbox
The second class in the example above class bar, is essentially what you are doing.
PS. forgot to scroll down on your post at first ... lol ... So I had to update my answer.
Bonus
You can also simplify the load code like this:
public function load($data)
{
foreach($data as $prop=>$value){
if(property_exists($this,$prop)) $this->$prop = $value;
}
return $this;
}
This way if you add new properties you don't have to edit the load method ever again, you just have to name the array elements the same as the class properties. You can even throw an error if the property does not exist if you want, by adding an else to the condition etc...
Personally, when I do this I prefer to call a set method like this:
//eg. $data = ['foo' => '2019-06-16']
public function load(array $data)
{
foreach($data as $prop=>$value){
$method = 'set'.$prop; //$method = 'setfoo' using the example above
if(method_exists($this,$method )){
$this->$method($value); //calls 'setfoo' with '2019-06-16'
}else{
throw new Exception('Unknown method '.$method);
}
}
return $this;
}
public function setFoo($date){
$this->foo = new DateTime($date);
}
Then you can apply some transforms to the data etc... PHP method names are not case sensitive. You can even combine these by first checking for a method then a property then throw the error etc...
Cheers.

Access attributes inside class

What I have is a product class, you can get a product via its id or its product nr. So I have created 2 constructors. The class is retrieving the product via the database and mapping the result to the class variables.
class Partnumber extends CI_Model
{
private $partNr;
private $description;
private $type;
public function __construct() {
}
public static function withId( $id ) {
$instance = new self();
$instance->loadByID( $id );
return $instance;
}
public static function withNr($partnumber) {
$instance = new self();
$instance->getIdFromPartnumber($partnumber);
return $instance;
}
protected function loadByID( $id ) {
$instance = new self();
$instance->getPartnumberFromId($id);
return $instance;
}
private function getIdFromPartnumber($partnumber){
$this->db->select("*");
$this->db->from('part_list');
$this->db->where('part_number', $partnumber);
$query = $this->db->get();
return $query->result_object();
}
//get the partnumber from an part id
private function getPartnumberFromId($partId){
$this->db->select("*");
$this->db->from('part_list');
$this->db->where('id', $partId);
$query = $this->db->get();
$this->mapToObject($query->result());
}
private function mapToObject($result){
$this->partNr = $result[0]->Part_number;
$this->description = $result[0]->Description;
$this->type = $result[0]->Type;
}
public function toJson(){
return json_encode($this->partNr);
}
}
The mapping works, (I know, I have to catch the errors). But all the values are null when I calling the toJson method.
I call it like this:
class TestController extends MX_Controller{
public function __construct(){
parent::__construct();
$this->load->model('Partnumber');
}
public function loadPage() {
$p = Partnumber::withId(1);
echo $p->toJson();
}
}
And yes, I know for sure that data is coming back, because I can print all the items in the mapping method. But why is the data gone when I acces it via toJson?
Your method withId calls loadByID which creates a new instance of your model. It does not load the data into the model that was created in withId which is returned

When I refresh the page my array is back to original, singleton not working?

It doesn't save my changes after adding a category.
If I add a category, it is seen in the overview, but if I refresh I see the original amount.
I guess there is an error in my Singleton-design but I can't seem to find it.
class ProductService {
private $_database;
public function __construct($databaseType) {
$databaseFactory = new DatabaseFactory();
$this->_database = $databaseFactory->createDatabase($databaseType);
}
public function addCategory($category){
$this->_database->addCategory($category);
}
public function getAllCategories() {
return $this->_database->getAllCategories();
}
}
class DatabaseFactory {
public function __construct() {
}
public function createDatabase($type){
switch ($type) {
case "Memory" :
return MemoryDatabase::getInstance();
}
}
}
class MemoryDatabase {
private $categories;
private function __construct() {
$this->categories = array(
new Category("Cheese"),
);
}
public static function getInstance() {
static $inst = null;
if ($inst === null) {
$inst = new MemoryDatabase();
}
return $inst;
}
private function __clone() {}
private function __wakeup() {}
public function addCategory($category) {
array_push($this->categories, $category);
}
public function getAllCategories() {
return $this->categories;
}
}
Each request you perform in PHP is stateless.
If you want to persist data between requests, you will need to put your data in some form of persistant storage, i.e., sessions, filesystem, database, memory, etc.
Singleton pattern only ensures a single copy of an object is created, for a given request.

Categories