PHP cast object in simple ORM - php

I would like to make a simple ORM in PHP for standard CRUD interaction with my db, I also want make it work in php5 for legacy compatibility.
I've written some classes to do this and it works, but not completely as I would.
This is the idea. I have an abstrac class called ModelBase which has a property (tableName) and some metods like select, insert, update and delete, plus has an abstract method, getData, that will be implemented by the classes that will be implement ModelBase and should return object of correct type.
So, for example, I could have a class Users which implements ModelBase and one another class UserData which is the model with the property.
Here is the code:
abstract class ModelBase{
private $tableName;
public function __construct($tableName) {
$this->tableName = $tableName;
}
public function select{
// make select query to db and retreive data
// ...
$resData = [];
while($dataRow = mysqli_fetch_array($res, MYSQLI_ASSOC)) {
$resData[] = $this->getObjectData($dataRow); // implemented in child class
}
return $resData;
}
public function insert(){ /* ... */}
public function update(){ /* ... */}
public function delete(){ /* ... */}
abstract function getObjectData($data); // maps the results
}
class UserData {
public $id;
public $name;
public $surname;
public $email;
// other fields
public function __construct() {}
}
class User implements ModelBase {
private $tableName = 'users';
public function __construct() {
parent::__construct($this->tableName);
}
public function getObjectData($dataRow) {
$o = new UserData ();
// mapping dataRow to object fields
$o->id = $dataRow['ID'];
// ....
return $o;
}
}
So I use my classes in this way:
$users = new Users();
$u = users->select();
$firstUser = $u[0]; // I get my user if exists
In $firstUser I'll get my object with property and correct data but I would like to have that also my IDE (vsCode in this case) would recognize the object type in order to suggest the correct properties. So if I write $firstUser-> I would like to see field suggestions (id, name, surname, ...) from UserData and for other xyzData classes as well.
What I should do to improve my classes in order to see property suggestions when I use my objects, also in php5?

Solution for PHP 8, tested on PHPStorm.
<?php
class Base {
/**
* #return static[]
*/
public function select() : array {
return [new self];
}
public function selectFirst() : static {
return $this->select()[0];
}
}
class User extends Base {
public ?string $userName = null;
}
#detects the current class via () : static
(new User)->selectFirst()->userName;
#detects the current class via #return static[]
(new User)->select()[0]->userName;
In line solution for PHP 5, define the variable directly with this comment
/** #var $a User */
$a->userName;
There is no benefit of supporting old PHP 5. You lose so mutch clean code and modern approach when supporting old php versions.
But when you have to, then go with the inline solution.
Not tested and not so clean for PHP 5:
class User extends Base {
public ?string $userName = null;
/**
* #return User[]
*/
public function select() : array {
return parent::select();
}
}

Related

PHP - Classes inheritance and combining with another class

I want to write something inheriting classes in the structure of the 3 levels. Abstract example of such.
couriers_list -> courier -> courier_name
class couriers_list {
// I'm searches courier_name of the database and reference to the class of courier_name found in database
public function show_courier() {
return $this->xxx();
}
}
class courier extends couriers_list {
// Function for the courier
// Probably it would interface or abstraction
}
class courier_name extends courier {
// Methods of accurately courier eg. Calculation associated with the delivery
// Here each courier would separate file php eg. ups.php
public function xxx() {
return 'xxx';
}
}
$k = new couriers_list();
echo $k->xxx();
I don't think I can extends courier to courier_list, because I don't have access to methods descendants. From the outside, I would like to refer only to the class couriers_list, and she has to take the info from the database and take methods resulting from courier_name.
How to put this problem to organize object?
Your class Courrier should be an interface since it used to define the way your Courrier should be used
interface CourrierInterface
{
const CLASS_NAME = __CLASS__;
public function getName();
public function getPrices();
public function XXX();
public function calculateDelivery($from, $to);
/** these are just example add here all the method that define a courier */
}
Then for each courrier you have, you should implements de CourrierInterface
class UpsCourrier implements CourrierInterface
{
private $name;
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
public function XXX()
{
// do something
}
}
I don't think the CourrierList should know about the database but just need to contains all the courrier object you have.
Here i used the project
https://github.com/yvoyer/collection
in order to manage the collection (TypedCollection class), it allows to control the type of class you want in your collection (here courrierInterface), but you can use an array if you want it much simple.
I create two XXX method, one for a specific courrier (since its a list) and another for apply to all the list. I don't know your need so take what you need
class CourrierList
{
/**
* TypedCollection
*/
private $courrierList;
public function __construct()
{
// i prefer to use a collection class that valid my type instead of an array, your choice!
$this->courrierList = new TypedCollection("CourrierInterface");
}
public function addCourrier(CourrierInterface $courrier)
{
$this->courrierList->add($courrier);
}
public function removeCourrier(CourrierInterface $courrier)
{
$this->courrierList->removeElement($courrier);
}
protected function getCourrierByName($courrierName)
{
$closure = function (CourrierInterface $courrier) use ($courrierName) {
return $courrier->getName() == $courrierName;
};
return $this->courrierList->filter($closure)->toArray();
}
// since its a list, you must be able to select your courrier to execute the XXX() method
public function XXXByName($name)
{
$courrier = $this->getCourrierByName($name);
$courrier->XXX();
}
// if you prefer to apply to all your courrierList just do
public function XXX()
{
foreach ($this->courrierList as $courrier) {
$courrier->XXX();
}
}
}
Since i don't think the CourrierList should know about the database, you can create a factory that does fetch the data in your db and create all the courriers. Here dbConnection is your choice, pdo etc... i just made an example, find the good way to make query.
class CourrierListFactory
{
private $dbConnection;
public function __construct(DbConnection $connection)
{
$this->dbConnection = $connection;
}
public function createCourrierList()
{
$results = $this->dbConnection->query(".....")->getResults();
$courrierList = new CourrierList();
foreach ($results as $result) {
$courrier = null;
switch ($result['name']) {
case 'ups':
$courrier = new UpsCourrier();
break;
case 'fedex':
$courrier = new FedexCourrier();
break;
/** and so on ... */
default:
// maybe throw exception if courier is not handle
}
$courrier->setName("....");
/** prepare your object here */
// add the courrier to the list
$courrierList->addCourrier($courrier);
}
return $courrierList;
}
}
Finally its how you use all of this, first create a dbCOnnection, then build your list, then you can acces
$dbConnection = new DbConnection();
// the factory allows you to separate the database from the List class and may be to generate
// your list in an other way later (using apis, etc...)
$factory = new CourrierListFactory($dbConnection);
$courrierList = $factory->createCourrierList();
$courrierList->XXX(); // apply to all
$courrierList->XXXByName("ups"); // apply just to ups courrier

Derived Classes in Php Phalcon

I am fairly new to php phalcon and the language itself. I am making a website that involves an abstract class and derived classes.
The Abstract Class:
<?php
abstract class UsersAbstract extends \Phalcon\Mvc\Model {
private $Full_Name; //
private $Mobile_Number; //
private $Email_ID; //
private $City; //
private $Country; //
private $DoB; //
private $Gender; //
private $Age; //
private $Availability_Flag; /*value of flag is 1 for active and 0 for inactive*/
/********setters and getters for all data members which I have not written here*******/
public function getSource()
{
return 'users_abstract';
}
}
The derived class
<?php
class UserPatients extends UsersAbstract
{
private $Patient_ID;
private $Guardian_Name;
private $Doctors = array();
public function getSource()
{
return 'user_patients';
}
/***************setter and getter for patient id*******************/
public function getPatientID()
{
return $this->Patient_ID;
}
public function setPatientID($value)
{
$this->Patient_ID = $value;
}
/****************setter and getter for guardian name******************/
public function getGuardianName()
{
return $this->Guardian_Name;
}
public function setGuardianName($value)
{
$this->Guardian_Name = $value;
}
/****************getter and setter for doctor array*********************/
public function getDoctors()
{
return $this->Doctors;
}
public function setDoctors($value)
{
$this->Doctors = $value;
}}
In my controller, I am reading data from a form in a view. An object of the class UserPatients is declared and its data members are set. Here is the action method that I am calling in the controller PatientSignInController:
public function SaveAction()
{
if (!empty($_POST)){
$patient_data = new UserPatients();
$patient_data->setFull_Name($_POST['Full_Name']);
$patient_data->setGuardianName($_POST['Guardian_Name']);
$patient_data->setDoB($_POST['DoB']);
$patient_data->setAge($_POST['Age']);
$patient_data->setGender($_POST['gender']);
$patient_data->setMobile_Number($_POST['Contact_No']);
$patient_data->setEmail_Address($_POST['Email_ID']);
$patient_data->setCity($_POST['City']);
$patient_data->setCountry($_POST['Country']);
$patient_data->setPatientID($_POST['Patients_ID']);
$patient_data->setFlag(1);
$doctorsArray = array("ACD","ABC"); //some hard coded values for the time being
$patient_data->setDoctors($doctorsArray);
$patient_medical_info = new PatientInfo();
$patient_medical_info->setBP($_POST['BP']);
$patient_medical_info->setTemperature($_POST['Temperature']);
$patient_medical_info->setInformation($_POST['Info']);
$patient_medical_info->setPatientID($_POST['Patients_ID']);
$patient_medical_info->setDateOfMeeting(date("d/m/y"));
$patient_data->save();
$patient_medical_info->save();
$this->response->redirect("../../../../WebMCare/PatientSignIn/Index");
}
}
The structure of my database is the same as these classes - I have a table Users_Abstract with all the attributes of the abstract class and I have a table User_Patients with all the attributes of the child class.
When I hit the save button on the form in the view and check the database tables, I discover that the new entries are added in the patient_info table. Which means that the $patient_medical_info->save() is working.
However, the table users_abstract does not contain any new data for the corresponding object, while the table user_patients does get the associated data. I checked messages for errors in save method, but it returned nothing.
I can't seem to find anything online.
Please help me.
I have tried storing data in a single table by associating one table, with all 12 attributes, to the classes UsersAbstract and User_Patients. Even that does not work.

Doctrine2 strange persist exception

I'm having strange problems when trying to persist a class of User that has a reference to many UserProperties. Note that a UserProperty will be managed by a cascade:persist.
UserProperties itself has a reference to a Property.
When creating a new User with a new UserProperty (which itself has a reference to an existing Property) it throws strange (strange as in i didn't expect it) error:
InvalidArgumentException: A new entity was found through the relationship 'UserProperty#property' that was not configured to cascade persist operations for entity
User:
class User extends IdentifiableObject {
// … other vars
/**
* #OneToMany(targetEntity="UserProperty", mappedBy="user", cascade={"persist", "remove"}, orphanRemoval=true)
*/
private $userProperties = null;
public function __construct() {
$this->userProperties = new ArrayCollection();
}
// … other methods
public function getUserProperties() {
return $this->userProperties;
}
public function setUserProperties($userProperties) {
$this->userProperties = $userProperties;
}
public function addUserProperty(UserProperty $userProperty) {
$userProperty->setUser($this);
$this->userProperties[] = $userProperty;
}
}
UserProperty:
class UserProperty extends IdentifiableObject {
/**
* #OneToOne(targetEntity="Property")
* #JoinColumn(name="propertyID")
*/
private $property;
public function getProperty() {
return $this->property;
}
public function setProperty($property) {
$this->property = $property;
}
}
Property class has no references to either class.
And finally my testClass using PHPUnit:
class UserDaoTest extends PHPUnit_Framework_TestCase {
private static $userDao;
private static $propertyDao;
public static function setUpBeforeClass() {
//this will make the EntityManager called inside our DAOImpl point to our test database...
define('__DBNAME__', 'db_test');
createCleanTestDatabase();
self::$userDao = new UserDaoImpl();
self::$propertyDao = new PropertyDaoImpl();
}
public function testEntityClassVariable() {
$this->assertEquals("User", self::$userDao->getEntityClass());
}
public function testPersistUserWithoutProperties() {
$user = new User();
$user->setUserName("tester1");
$user->setUserType(1);
self::$userDao->persist($user);
self::$userDao->flush();
$this->assertEquals(1, count(self::$userDao->findAll()));
}
public function testPersistUserWithProperties() {
$user = new User();
$user->setUserName("tester2");
$user->setUserType(1);
$property = new Property();
$property->setName("propertyName");
$property->setType(1);
self::$propertyDao->persist($property);
self::$propertyDao->flush();
$userProperty = new UserProperty();
$userProperty->setProperty($property);
$userProperty->setValue("test");
$user->addUserProperty($userProperty);
self::$userDao->persist($user);
self::$userDao->flush();
$this->assertEquals(2, count(self::$userDao->findAll()));
$userInDB = self::$userDao->find($user);
$this->assertNotNull($userInDB);
$this->assertEquals(1, count($userInDB->getUserProperties()));
}
}
The strange thing is that the Property is indeed created in the Database.
Also the test works perfectly fine IF i use the userDao->persist to save the Property (instead of the propertyDao...
Any help would be appreciated, thanks in advance!
The problem was that i was using a different entityManager in each dao so effectively having a different UnitOfWork for each DAO. When i made the entity a singleton so that each DAO had the same reference to it.

Magento - UnitTests - Mock Objects

I am writing some tests for a Magento module, using Ivan Chepurnyi's extension, and I'm having trouble using the mock objects.
Here is the class:
<?php
class Namespace_Module_Block_Class extends Mage_Core_Block_Template
{
private $_salesCollection;
public function __construct()
{
$this->_salesCollection = Mage::getModel('module/classA')->getCollection()
->addFieldToFilter('id', $this->_getId());
}
public function _getId()
{
return Mage::getModel('module/classB')->getId();//session params
}
public function getSalesTotalNumber()
{
return $this->_salesCollection->count();
}
}
The method I'm trying to test is getSalesTotalNumber().
And here is the test:
<?php
class Namespace_Module_Test_Block_Class extends EcomDev_PHPUnit_Test_Case
{
private $_mock;
public function setUp()
{
$this->_mock = $this->getMock('Namespace_Module_Block_Class',
array('_getId')
);
$this->_mock->expects($this->any())
->method('_getId')
->will($this->returnValue(1024));
parent::setUp();
}
/**
* #test
* #loadFixture
* #loadExpectation
*/
public function testSalesTotalNumber()
{
$actual = $this->_mock->getSalesTotalValue();
$expected = $this->_getExpectations()->getSalesTotalNumber();
$this->assertEquals($expected, $actual);
}
}
As you can see, what I want to do is overwrite the _getId() method so that it returns an id which match the id in the fixture and so load the collection. But it doesn't work :-(.
In my test, if I echo $this->_mock->_getId() it returns the correct Id (1024). But in the __construct() of my class $this->_getId() returns null, which is the expected value during testing (I mean, during testing there is no session, so it can't get the object's Id as I store it in a session variable). So the _getId() method isn't mocked by my test case.
Any help will be highly appreciated.
So my problem was not in the mock/test but in the class.
I have moved the content of __construct() into a protected method which returns the collection object. That's how my class looks like now:
<?php
class Namespace_Module_Block_Class extends Mage_Core_Block_Template
{
private $_salesCollection;
protected function _getAffiliateSales()
{
if (is_null($this->_salesCollection)) {
$affiliateId = $this->_getId();
$this->_salesCollection = Mage::getModel('module/classA')
->addFieldToFilter('id', $affiliateId);
}
return $this->_salesCollection;
}
public function _getId()
{
return Mage::getModel('module/classB')->getId();//session params
}
public function getSalesTotalNumber()
{
return $this->_getAffiliateSales()->count();
}
}

How to override a static property of a parent object and let the parent object access the new value in PHP?

This is what I have: All objects that can be persisted on the database extend the DatabaseObject abstract class, which has all the logic code to actually watch for attribute changes and run the databas queries.
I'm using two static variables to define object-specific details. I define them generically in the base class, and then supposedly I overwrite them in the actual database objects.
The problem is: When the code in the parent class is actually executed, it uses the old parent value instead of the current object value.
Here's the code for the base class:
abstract class DatabaseObject {
public $id;
private static $databaseTable = NULL;
private static $databaseFields = array();
private $data = array();
private $changedFields = array();
public function IDatabaseObject($id) {
$this->id = $id;
$this->data = Database::GetSingle(self::$databaseTable, $id);
Utils::copyToObject($this, $this->data, self::$databaseFields);
}
public static function Load($id) {
return new self($userID);
}
public static function Create($data) {
$id = Database::Insert(self::$databaseTable, $data);
return new self($id);
}
public function Save() {
$data = Utils::copyFromObject($this, $this->changedFields);
Database::Update(self::$databaseTable, $data, $this->id);
}
public function __constructor() {
// We do this to allow __get and __set to be called on public vars
foreach(self::$databaseFields as $field) {
unset($this->$field);
}
}
public function __get($variableName) {
return $this->$variableName;
}
public function __set($variableName, $variableValue) {
// We only want to update what has been changed
if(!in_array($variableName, $this->changedFields) && in_array($variableName, self::$databaseFields)) {
array_push($this->changedFields, $variableName);
}
$this->$variableName = $variableValue;
}
}
And here's the code for one of the objects extending the base class above:
class Client extends DatabaseObject {
public static $databaseTable = "clients";
public static $databaseFields = array("name","contactName","primaryUserID","email","is_active","rg","cpf","cnpj","ie","addrType","addrName","addrNumber","addrComplement","addrPostalCode","addrNeighborhood","addrCity","addrState","addrCountry","phoneLandline","phoneFax","phoneMobile");
public $name;
public $contactName;
public $primaryUserID;
public $email;
public $is_active;
public $rg;
public $cpf;
public $cnpj;
public $ie;
public $addrType;
public $addrName;
public $addrNumber;
public $addrComplement;
public $addrPostalCode;
public $addrNeighborhood;
public $addrCity;
public $addrState;
public $addrCountry;
public $phoneLandline;
public $phoneFax;
public $phoneMobile;
public static function Load($id) {
return new Client($id);
}
}
What am I doing wrong here? Is there another way I can achieve the same result?
A brief addendum: I declare the attributes in the class body mainly to let it be seen by the NetBeans' auto-complete feature.
You are looking for Late Static Binding.
So you need to use:
static::$databaseTable
instead of
self::$databaseTable
This feature is available as of PHP 5.3. Simulating this in PHP 5.2 is very hard, because of two reasons: get_called_class is available only since PHP 5.3, too. Therefore it must be simulated, too, using debug_backtrace. The second problem is, that if you have the called class, you still may not use $calledClass::$property because this is a PHP 5.3 feature, too. Here you need to use eval or Reflection. So I do hope that you have PHP 5.3 ;)

Categories