I'm not entirely sure the best way to handle a status field that relates to integers in a database table within a class.
Say we have the following:
$status = array(1 => 'Active', 2 => 'Inactive', 3 => 'Cancelled');
I'm thinking for clarity in the system, I want class constants and a getter for retrieving the status in an associative array, and so the class uses something better defined than "1" or "2".
class User {
const USER_STATUS_ACTIVE = 1;
const USER_STATUS_INACTIVE = 2;
const USER_STATUS_CANCELLED = 3;
public function getStatusList() {
return array(User::USER_STATUS_ACTIVE => 'Active',User::USER_STATUS_INACTIVE => 'Inactive',User::USER_STATUS_ACTIVE => 'USER_STATUS_CANCELLED');
}
}
This then allows setting the status using the constants with:
class User {
private $status_id;
const USER_STATUS_ACTIVE = 1;
const USER_STATUS_INACTIVE = 2;
const USER_STATUS_CANCELLED = 3;
static public function statusList() {
return array(User::USER_STATUS_ACTIVE => 'Active',User::USER_STATUS_INACTIVE => 'Inactive',User::USER_STATUS_CANCELLED => 'Cancelled');
}
public function setStatus($status_id) {
try {
if (!key_exists($status_id, User::statusList())) throw new Exception('Invalid status ID');
$this->status_id = $status_id;
}
catch (Exception $e) {
die($e);
}
}
}
$a = new User();
$a->setStatus(User::USER_STATUS_ACTIVE);
Alternately methods can be created for setActive(), setInactive(), setCancelled().
But what I'm trying to figure out is how to best handle the actual status values.
Would it be better to break the statuses out to a static class?
class User {
private $status_id;
public function setStatus($status_id) {
try {
if (!key_exists($status_id, UserStatuses::statusList())) throw new Exception('Invalid status ID');
$this->status_id = $status_id;
}
catch (Exception $e) {
die($e);
}
}
}
class UserStatuses {
const USER_STATUS_ACTIVE = 1;
const USER_STATUS_INACTIVE = 2;
const USER_STATUS_CANCELLED = 3;
static public function statusList() {
return array(UserStatuses::USER_STATUS_ACTIVE => 'Active',UserStatuses::USER_STATUS_INACTIVE => 'Inactive',UserStatuses::USER_STATUS_CANCELLED => 'Cancelled');
}
}
Or is there something entirely different that would be better?
Using the ReflectionClass
I like you second example; it's a very simple and effective way to make an enum type (sort of). Actually, some of the pretty big php ORMs out there, like Doctrine operate using a similar pattern. The one thing I like to do to improve scalability is to use the ReflectionClass. This will allow you to add new values in the form of constants without having to change the function that returns the list of values. I would also refactor the code to check if the value is valid in the enum class as well to keep a better separation of concerns between the two classes
class User {
private $status_id;
public function setStatus($status_id) {
if (UserStatuses::isStatus($status_id)) {
$this->status_id = $status_id;
}
else {
throw new Exception('invalid status');
}
}
}
class UserStatuses {
const USER_STATUS_ACTIVE = 1;
const USER_STATUS_INACTIVE = 2;
const USER_STATUS_CANCELLED = 3;
public static function statusList() {
$oClass = new ReflectionClass(__CLASS__);
return $oClass->getConstants();
}
public static function isUserStatus($int) {
return in_array($int, self::statusList());
}
}
Referencing database IDs is not a good practice, in that the values of IDs are generally controlled by the database itself. It's true to say that no database column value is static, but the ID column on a database table is generally an internal ID that is auto-incremented and whose FK references are maintained via cascades in UPDATE and DELETE operations. In other words, the ID is the domain of the database, not your code.
A better practice is to include a custom unique field for your code, like this (I'm assuming MySQL here):
ALTER TABLE `my_reference_table` ADD COLUMN `internalCode` VARCHAR(256);
UPDATE `my_reference_table` SET `internalCode` = 'Active' WHERE `id` = 1;
UPDATE `my_reference_table` SET `internalCode` = 'Inactive' WHERE `id` = 2;
UPDATE `my_reference_table` SET `internalCode` = 'Cancelled' WHERE `id` = 3;
ALTER TABLE `my_reference_table` ALTER COLUMN `internalCode` VARCHAR(256) NOT NULL UNIQUE;
Once you've got a database data setup like this, then you can treat the column internalCode as a static element, have PHP constants that match those internal codes, and be sure that you're referring to the same row no matter if the ID changes.
In terms of storing these internal codes in PHP, I generally use an abstract class with a final private constructor so that it's very very clear that the class is not to be extended, and to only be referenced only in the static context, like so:
class UserStatusConstants {
const _ACTIVE = 'Active';
const _CANCELLED = 'Cancelled';
const _INACTIVE = 'Inactive';
final private function __construct() {}
}
You might be asking at this point why the constant names are prefixed with an underscore - that's to avoid the issue of having constant names that clash with reserved words in PHP.
Anyhow, once you've got this setup, there's various techniques you can use to set user_status_id values in your user table. Here's three I can think of:
UPDATE with JOIN queries
Doing an UPDATE with a JOIN against the user status table, and filtering on the internal code (see How to do 3 table JOIN in UPDATE query?)
SELECT then UPDATE
Start with a SELECT query against the user status table, filtering on internal code, then using that result to feed the user status id to the UPDATE query against the user table. This technique incurs an extra query, but if you store the results of the first query in a cache (e.g. Memcached, or a third party library) then this can speed up all queries using that data in the long run.
Stored procedure
You could create a stored procedure that takes the internal code as a parameter, as well as any other parameters you need to pass to update other fields in the user table
Related
I'm really new in PHP, our instructor just teaching us C++ OOP and I want to try it on PHP.
I'm creating objects with my class.
class TwitterUser {
private $twittername;
public function TwitterUser($a)
{
$this->twittername = $a;
// echo $this->twittername;
}
}
$reader = new Spreadsheet_Excel_Reader($target_path);
$veriler = $reader->sheets[0]['cells'];
foreach($veriler as $veri)
{
if(!empty($veri[$sutun]) and $veri[$sutun]!="Twitter")
{
$kisiler[] = new TwitterUser(temizle($veri[$sutun]));
}
}
What I want is, if one object has same string with other object in $twittername data member, don't create new object.
This task is usually done using some kind of Model -> database approach (such as Doctrine), in which case you save the model data into database. The database table should be designed to not allow the same name for more than one record and the logic to enforce and error handle this can be built into the model class.
You can achieve the same by pure PHP, but it requires existing instances to be stored somehow so when creating new instances, existing ones can be checked for uniqueness.
You don't want to add the object if the username is test? Basically you can't back out of a constructor. Just add a simple flag to only add "test" user once.
Using your code sample:
$testuserexists = false;
foreach($veriler as $veri)
{
if(!empty($veri[$sutun]) and $veri[$sutun]!="Twitter" && $testuser == false)
{
$kisiler[] = new TwitterUser(temizle($veri[$sutun]));
if ($veri[$sutun] == "Test")
$testuserexists = true;
}
}
Or if you are trying to not have duplicates:
foreach($veriler as $veri)
{
if(!empty($veri[$sutun]) and $veri[$sutun]!="Twitter" && !isset($kisiler[$veri[$sutun]]))
{
$kisiler[$veri[$sutun]] = new TwitterUser(temizle($veri[$sutun]));
}
}
I don't know what the temizle function is supposed to do, but basically you can assign the username as the associative array key and prevent duplicates by adding an isset() to your conditional.
I've made this class to handle all of my sql-queries. But I'm unsure of how to use it properly.
The class looks something like this (this is a VERY simple version of it):
class sql {
private $conn;
private $data;
function __construct() {
//makes connection to DB and sets $conn and $data
}
public function select($variables, $table, $criterias) {
//returns an array with all the info from DB
}
function __destruct() {
//closes the sql-connection
}
}
The question now is: Is this going to overload the DB, if I use it multiple times on every page-load? (refered to as Example #1)
$dbInfo = (new sql)->select($var,$tab,$cri);
$moreInfo = (new sql)->select($var2,$tab2,$cri2);
$evenMoreInfo = (new sql)->select($var3,$tab3,$cri3);
Would it be beneficial to make my sql class's methods static?
Or should I not create a new instance of a sql object every time I want to make a query (like the example below - refered to as Example #2)?
$sql = new sql();
$dbInfo = $sql->select($var,$tab,$cri);
$moreInfo = $sql->select($var2,$tab2,$cri2);
$evenMoreInfo = $sql->select($var3,$tab3,$cri3);
How and when is Example #1 the better choice over Example #2, and vice versa?
If I assume that Example #1 is going to take the most resources from the DB, when would you pick Example #1 over Example #2?
Your example 2 is more common to see, however the SQL object is usually a static/singleton. So it connects to the database once per server request.
Your base SQL object should handle connecting to a database and then handle basic input/output, such as executing a string of SQL and returning the results.
You can then add new objects on top of that for each object/table than then interfaces with this SQL singleton. These classes will handle constructing their custom SQL based on their table, joins, field names/types, etc.
E.g:
A very basic 'table' object looks like this
class SomeTableObject
{
m_TableName = 'SomeTable'; // Table to get Data from
function GetSelectSQL()
{
return "SELECT * FROM ".$this->m_TableName;
}
function Select($where)
{
$sql = $this->GetSelectSQL().$where;
return SqlSingleton::Execute($sql);
}
function GetByID($id)
{
$where = " WHERE FieldNameForID=$id";
return $this->Select($where);
}
}
These objects work better if they extend a base class that has those basic GetSelectSQL, TableName, Select, etc functions. The GetByIDs (and other gets, updates, inserts) will vary from table to table.
I'm having some trouble updating a MySQL database via a PHP class (this is my first real foray into PHP classes, so I might be missing some very basic elements). I've included the code that is relevant to the problems I am having (that is, not properly updating the MySQL table).
To quickly explain my code, I pull a user's information from the database when an object is constructed. Then, I call the function modify_column() to increase or decrease a numeric value from the data I've pulled. Finally, I run save() to update the MySQL table.
I am having two problems: (1) $this->info is not being updated properly by the modify function (for example, if I modify_column('age', '1'), a var_dump shows age int(3) rather than age string(2) = 10 (assuming the original age was 9). And (2), the update query is not working. I'm assuming it's because I have an improperly-sized variable stemming from the first issue.
A snippet of the code (my database functions are based on a PDO wrapper and they have always worked just fine):
class user {
public $id;
public function __construct($id) {
global $db;
/* pull the user's information from the database */
$bind = array(':id' => $id);
$result = $db->select('user', 'id = :id', $bind, '*', SQL_SINGLE_ROW);
$this->id = $result['id'];
$this->info = $result;
}
/*
* Update the user's MySQL table, thereby saving the data.
*/
public function save() {
global $db;
$bind = array($this->id);
$db->update('users', $this->info, 'id = ?', $bind);
}
public function modify_column($column, $amount) {
$this->info[$column] += $amount;
}
}
Also, please let me know if there is a neater way to do what I am trying to accomplish (that is, quickly modify numeric values in a table using class functions.
Thanks!
You seem to not have any provision for typing of your variables. When you add your data to $this->info all the value are going to be set as strings. You don't want to do incremental math (i.e. +=, -= etc.) on strings. You need to cast to these values as integer. What I suggest would be to add a class property having an array of your class properties and their types. You will then be able to cast all you values according to their types when setting the $info array.
So something like this
protected $fields = array(
'age' => 'integer',
'name' => 'string',
// etc.
}
Then you can add a function like this
protected function type_cast(&$value, $key) {
// field type to use
$type = $this->fields[$key];
if ($type === 'integer') {
$value = (integer)$value;
} else if ($type === 'string') {
$value = (string)$value;
} // etc.
}
And in your constructor, just walk $result through the type_cast function:
array_walk(&$result, array($this, 'type_cast'));
$this->info = $result;
You probably also need to make sure your id value cast as an integer if you are using an integer in the DB.
I am not sure how you DB abstraction works, so hard to tell what is happening. I would suggest echoing out the query itself and trying it against the database, or taking a look at the MySQL errors that are being returned to get a feel fro what is going wrong there.
I have 4 classes, each with their own database tables:
GlobalObject
Image
Project
Page
GlobalObject has 3 properties
global_object_id
added_by
datetime_added
Image, Project and Page have many different fields, they ALL have the following though:
id
global_object_id (Foreign key)
At the moment, my class structure has Images, Projects and Pages as subclasses of the GlobalObject class, this allows the 3 subclasses access to the variables that are required of them, datetime_added etc.
How should I set the properties using PHP and MySQL? At the moment, ALL of the fields (including those in the global_object table) are in each section's table (except global_object_id which cannot exist without the new table - and is why I need to do this), So i request the data from the Image table for example, which includes all the properties in the parent class and is set using parent::set_by_row($database_args), and $this->set_by_row($database_args);
At the moment I do the following (Using Image as an example):
class Image extends GlobalObject
{
$id;
$title;
function set_by_id($id)
{
// Select Q from Image table
$this->set_by_row($db_result);
}
function set_by_row($args)
{
parent::set_by_row($args);
// Set class properties
}
}
To reiterate: I want to be able to do the following:
$image = new Image()
$image->set_by_global_object_id(23);
And the image be set, including everything in the parent class (which is stored in a separate table)
Please ask away if any of that is unclear.
A very convenient way is offered by those "magic methods", like __get and __set. The following example provides read-only access to the data, but can easily be extended with a __set method.
abstract class GlobalObject {
protected $_data = array();
/* Magic Method: Enable read access via $this->name */
public function __get($key) {
if (isset($this->_data[$key])) {
return $this->_data[$key];
}
throw new Exception('Property "'. $key .'" not found.');
}
protected function _setFromArray(array $data) {
// .. do whatever needs to be done with the data ..
$this->_data = $data;
}
}
Now just extend this class:
class Image extends GlobalObject {
public function setByGlobalId($id) {
$result = mysql_query('SELECT i.*, g.* FROM images AS i
LEFT JOIN global_objects AS g
ON g.global_object_id = i.global_object_id
WHERE i.global_object_id = '. intval($id) .' LIMIT 1');
// #todo check if result is empty
$data = mysql_fetch_assoc($result);
$this->_setFromArray($data);
}
}
And a simple example:
$image = new Image();
$image->SetByGlobalId(42);
echo $image->added_by;
How can I do this in OO PHP:
A form ('in newstudent.php') asks the user to enter his name, course and year.
After selecting 'Submit' button, the page will go to 'records.php'
records.php - contains a table that displays all the records (columns: name, course, year)
when the user selects 'Submit', the new record will be added to the database which has a table named STUDENTS
SQL code
CREATE TABLE STUDENTS(
NAME VARCHAR(25) NOT NULL,
COURSE VARCHAR(25) NOT NULL,
YEAR INT NOT NULL,
CONSTRAINT STUDENTS_PK PRIMARY KEY(NAME));
*please don't mind about the primary key coz i know it's not accurate to use name as the primary key. this is just for exmple purposes.
and also...How can i manipulate data in DB using OO PHP?
Thanks
Read a book
Search Google
Create Student Object
Create Database Object
Query Database Object to insert Student Object
Well, if you want to switch to a OO method of representing students in a database, how about a 'Student' class that looks something like the definition below (although this is very basic, and not a full ORM in any way). It takes you halfway to an ActiveRecord style approach.
Note that I have assumed you will use an integer id column, not doing so makes the whole class annoying.
class Student {
var $id = -1;
var $name;
var $course;
var $year;
public static function newFromID ($id)
{
//fetch a row ($row) from the students table matching the given id
//perhaps returning false if the student doesn't exist?
return self::newFromRow($row);
}
// this method should return a new student object given a specific db row
// and should be called from newFromID. This function means that if the table
// changes, modifications only have to be made in one place
public static function newFromRow($row)
{
$obj = new Student();
//fill in the fields of the object based on the content of the row
return $obj;
}
public static function getAllStudents()
{
//perhaps return an array of student objects, by doing a broad select,
//and passing each row to newFromRow?
}
//this should save the object to the database, either inserting or updating as appropriate
public function save()
{
if($this->id == -1)
{
//insert, store the auto_increment id in $this->id
} else {
//update
}
}
}
So, to create a new student, and save it to the database:
$student = new Student();
$student->name = "John Smith";
$student->course = "French";
$student->year = 2;
$student->save();
In reality, it is often more sensible to use an existing ORM system, but if that isn't an option, you can consider writing your own.
Maybe you talk about ORM - Object Relation Mapping patterns? There are many different approaches to get mapped SQL data objects to PHP classes: Propel, Doctrine (both can be used with Symfony framework), ActiveRecord.
Of course, you can try to implement your own ORM system. You need to write data access layer for this ORM, classes which describes SQL tables and many other things. It is very interesting (for educational purposes).