Is the switch-function SQL-injection safe? - php

I'm trying to access entries from the database with variable column names.
I have this table containing vehicles that can belong to one of three categories (car, bike, truck):
vehicle
car
bike
truck
Car 1
x
Car 2
x
Bike 1
x
Truck 1
x
With OOP and PDO, I'm trying to access the vehicles that belong to a category. Like so:
User-input:
URL: ?category=cars
All of the following is inside a class called "Vehicles".
Constructor of class Vehicles:
public function __construct() {
$this->category = $_GET["category"] ?? "cars";
switch ($this->category) {
default: //Avoiding db-error messages by setting default category to "car"
case "cars":
$this->category = "car";
break;
case "bikes":
$this->category = "bike";
break;
case "trucks":
$this->category = "truck";
break;
}
I then access the entries from the db that correspond to the category:
public function getVehiclesFromCategory() {
$sql = "SELECT * FROM vehicles WHERE $this->category IS NOT NULL";
$stmt = $this->connect()->query($sql);
while ($row = $stmt->fetch()) {
$row["vehicle"]."<br>";
}
}
I then create the object to get the output from the chosen category:
$Vehicles = new Vehicle();
$Vehicles->getVehiclesFromCategory();
I'm basically relating the user input to a predefined value. Is this sufficient to avoid SQL-injections?
I do realize I'm using a bad db-design, as the user is not supposed to get any hints about the names of the db-columns. I am also aware that I should avoid db-related error messages that can be useful for hackers (which is why I use the default-switch) - but I need a quick fix for now with the current db-model.

Short answer:
Yes
Long answer:
Yes you are,
because you dont actualy use the user input as an database field they cant manipulate it.
Aslong you doesnt directly put user input into your database you wont get problems with mysql injections.
Many People tell you to use prepared statments at every request, but you only have to use them if you directly use userinput in your querys like a username order an email.

It looks as though this question ("is it safe?") has been answered already. However, the how and why seem to be a bit up in the air so here's some further information...
Why it's safe
The ONE thing that is saving you from SQL injection in this case is the fact that you set a default case in your switch statement. Without that one word you would be wide open to SQL injections. Let's play it out:
Valid input example
User input bikes
Your code sets the category property to bikes
Your switch runs and bikes is found so it returns bike as the category
Invalid input example
User input hairStraightener
Your code sets the category property to hairStraightener
Your switch runs and hairStraightener isn't found so it returns car as the category; which is the default case
Invalid case without default
User input hairStraightener
Your code sets the category property to hairStraightener
Your switch runs and hairStraightener isn't found so category isn't updated and remains as hairStraightener
Now, imagine a user had put something like:
1; DROP TABLE vehicles; --
// OR...
1; UPDATE TABLE vehicles SET price = 1; --
Now you've lost a load of data or everything in your shop costs £1 (bargain!)
Improving things
You're on the right lines: if you need to input a variable directly into the SQL query you need to whitelist acceptable items and only use those. There are different ways of doing it...
As you have with a switch/case
With a match (PHP 8+)
With an array and lookup
By checking variables against the DB schema
Switch
The biggest problem I see with the way you've done is that if someone comes along and looks at your code they may well see that you've effectively set a default before the switch and therefore remove the default case; which would leave you open to SQL injection.
So you should update your code accordingly:
Don't ever set the user input to the category property
Set the default property of category on declaration
E.g.
public $category = "car";
public function __construct()
{
switch ($_GET["category"] ?? null) {
case "cars":
$this->category = "car";
break;
case "bikes":
$this->category = "bike";
break;
case "trucks":
$this->category = "truck";
break;
}
}
Match
As commented by #Dharman if your server is running PHP 8+ you can use match. Which, in this case, you can think of as a type sensitive switch statement:
Note: match will throw an error if you don't supply the default case; or rather if a value is supplied that can't be matched!
function __construct()
{
$this->category = match($_GET["category"] ?? "cars") {
"cars" => "car",
"bikes" => "bike",
"trucks" => "truck",
default => "car"
};
}
Array lookup
private $allowedFields = [
"cars" => "car",
"bikes" => "bike",
];
public function __construct()
{
$this->category = $this->allowedFields[$_GET["category"] ?? "cars"];
}
DB Schema
Finally, you could auto-generate the safe fields by checking the DB schema (something like DESCRIBE vehicles) and checking the input matches one of the column names. In your case though this probably isn't the best idea because you could still have someone input a real field which wasn't intended. Maybe it wouldn't be catastrophic, but it definitely isn't intended!
Fixing things long term
As others have said this is a largely flawed DB design. Presumably looking something like:
vehicles
id
make
model
price
...
bike
car
van
truck
...
When it should look more like:
vehicle < vehicleType > type
id id id
make vehicle_id name
model type_id
price
...
You then update your SQL to look something like:
SELECT
vehicle.id, vehicle.make, vehicle.model, vehicle.price,
type.name
FROM vehicle
JOIN vehicleType on vehicle.id = vehicleType.vehicle_id
JOIN type on vehcileType.type_id = type.id
WHERE type.name = ?
Now you can used a prepared statement to be fully safe
N.B.
It may seem like creating two tables and updating the existing one into them is a time consuming process. But in reality it won't take that long. The process:
Create the tables with appropriate datatypes, properties, etc.
DESCRIBE the vehicles table and extract the different types (car, bike, etc.)
Insert the types into the table type
This can all be done automatically with a few lines of PHP, for example
Write a short script to loop through each line of your vehicles table and insert records into the vechicleType table based on what columns are not null
Check your data (backup as required)
Remove the no longer needed columns from vehicles

you are running a bad design when you take the coulmn as a parameter.
however, the code as is, would be safe because it's fixed.
please do at least.
replace :
$sql = "SELECT * FROM vehicles WHERE $this->category IS NOT NULL";
$param = $usetheconnection->real_escape_string($this->category);
$sql = "SELECT * FROM vehicles WHERE `{$param}` IS NOT NULL";
then redo the design

Related

Yii active record relation limit to one record

I am using PHP Yii framework's Active Records to model a relation between two tables. The join involves a column and a literal, and could match 2+ rows but must be limited to only ever return 1 row.
I'm using Yii version 1.1.13, and MySQL 5.1.something.
My problem isn't the SQL, but how to configure the Yii model classes to work in all cases. I can get the classes to work sometimes (simple eager loading) but not always (never for lazy loading).
First I will describe the database. Then the goal. Then I will include examples of code I've tried and why it failed.
Sorry for the length, this is complex and examples are necessary.
The database:
TABLE sites
columns:
id INT
name VARCHAR
type VARCHAR
rows:
id name type
-- ------- -----
1 Site A foo
2 Site B bar
3 Site C bar
TABLE field_options
columns:
id INT
field VARCHAR
option_value VARCHAR
option_label VARCHAR
rows:
id field option_value option_label
-- ----------- ------------- -------------
1 sites.type foo Foo Style Site
2 sites.type bar Bar-Like Site
3 sites.type bar Bar Site
So sites has an informal a reference to field_options where:
field_options.field = 'sites.type' and
field_options.option_value = sites.type
The goal:
The goal is for sites to look up the relevant field_options.option_label to go with its type value. If there happens to be more than one matching row, pick only one (any one, doesn't matter which).
Using SQL this is easy, I can do it 2 ways:
I can join using a subquery:
SELECT
sites.id,
f1.option_label AS type_label
FROM sites
LEFT JOIN field_options AS f1 ON f1.id = (
SELECT id FROM field_options
WHERE
field_options.field = 'sites.type'
AND field_options.option_value = sites.type
LIMIT 1
)
Or I can use a subquery as a column reference in the select clause:
SELECT
sites.id,
(
SELECT id FROM field_options
WHERE
field_options.field = 'sites.type'
AND field_options.option_value = sites.type
LIMIT 1
) AS type_label
FROM sites
Either way works great. So how do I model this in Yii??
What I've tried so far:
1. Use "on" array key in relation
I can get a simple eager lookup to work with this code:
class Sites extends CActiveRecord
{
...
public function relations()
{
return array(
'type_option' => array(
self::BELONGS_TO,
'FieldOptions', // that's the class for field_options
'', // no normal foreign key
'on' => "type_option.id = (SELECT id FROM field_options WHERE field = 'sites.type' AND option_value = t.type LIMIT 1)",
),
);
}
}
This works when I load a set of Sites objects and force it to eager load type_label, e.g. Sites::model()->with('type_label')->findByPk(1).
It does not work if type_label is lazy-loaded.
$site = Sites::model()->findByPk(1);
$label = $site->type_option->option_label; // ERROR: column t.type doesn't exist
2. Force eager loading always
Building on #1 above, I tried forcing Yii to always to eager loading, never lazy loading:
class Sites extends CActiveRecord
{
public function relations()
{
....
}
public function defaultScope()
{
return array(
'with' => array( 'type_option' ),
);
}
}
Now everything always works when I load Sites, but it's no good because there are other models (not pictured here) that have relations that point to Sites, and those result in errors:
$site = Sites::model()->findByPk(1);
$label = $site->type_option->option_label; // works now
$other = OtherModel::model()->with('site_relation')->findByPk(1); // ERROR: column t.type doesn't exist, because 't' refers to OtherModel now
3. Make the reference to the base table somehow relative
If there was a way that I could refer to the base table, other than "t", that was guaranteed to point to the correct alias, that would work, e.g.
'on' => "type_option.id = (SELECT id FROM field_options WHERE field = 'sites.type' AND option_value = %%BASE_TABLE%%.type LIMIT 1)",
where %%BASE_TABLE%% always refers to the correct alias for table sites. But I know of no such token.
4. Add a true virtual database column
This way would be the best, if I could convince Yii that the table has an extra column, which should be loaded just like every other column, except the SQL is a subquery -- that would be awesome. But again, I don't see any way to mess with the column list, it's all done automatically.
So, after all that... does anyone have any ideas?
EDIT Mar 21/15: I just spent a long time investigating the possibility of subclassing parts of Yii to get the job done. No luck.
I tried creating a new type of relation based on BELONGS_TO (class CBelongsToRelation), to see if I could somehow add in context sensitivity so it could react differently depending on whether it was being lazy-loaded or not. But Yii isn't built that way. There is no place where I can hook in code during query buiding from inside a relation object. And there is also no way I can tell even what the base class is, relation objects have no link back to the parent model.
All of the code that assembles these queries for active records and their relations is locked up in a separate set of classes (CActiveFinder, CJoinQuery, etc.) that cannot be extended or replaced without replacing the entire AR system pretty much. So that's out.
I then tried to see if I can create "fake" database column entries that would actually be a subquery. Answer: no. I figured out how I could add additional columns to Yii's automatically generated schema data. But,
a) there's no way to define a column in such a way that it can be a derived value, Yii assumes it's a column name in way too many places for that; and
b) there also doesn't appear to be any way to avoid having it try to insert/update to those columns on save.
So it really is looking like Yii (1.x) just does not have any way to make this happen.
Limited solution provided by #eggyal in comments: #eggyal has a suggestion that will meet my needs. He suggests creating a MySQL view table to add extra columns for each label, using a subquery to look up the value. To allow editing, the view would have to be tied to a separate Yii class, so the downside is everywhere in my code I need to be aware of whether I'm loading a record for reading only (must use the view's class) or read/write (must use the base table's class, does not have the extra columns). That said, it is a workable solution for my particular case, maybe even the only solution -- although not an answer to this question as written, so I'm not going to put it in as an answer.
OK, after a lot of attempts, I have found a solution. Thanks to #eggyal for making me think about database views.
As a quick recap, my goal was:
link one Yii model (CActiveRecord) to another using a relation()
the table join is complex and could match more than one row
the relation must never join more than one row (i.e. LIMIT 1)
I got it to work by:
creating a view from the field_options base table, using SQL GROUP BY to eliminate duplicate rows
creating a separate Yii model (CActiveRecord class) for the view
using the new model/view for the relation(), not the original table
Even then there were some wrinkles (maybe a Yii bug?) I had to work around.
Here are all the details:
The SQL view:
CREATE VIEW field_options_distinct AS
SELECT
field,
option_value,
option_label
FROM
field_options
GROUP BY
field,
option_value
;
This view contains only the columns I care about, and only ever one row per field/option_value pair.
The Yii model class:
class FieldOptionsDistinct extends CActiveRecord
{
public function tableName()
{
return 'field_options_distinct'; // the view
}
/*
I found I needed the following to override Yii's default table data.
The view doesn't have a primary key, and that confused Yii's AR finding system
and resulted in a PHP "invalid foreach()" error.
So the code below works around it by diving into the Yii table metadata object
and manually setting the primary key column list.
*/
private $bMetaDataSet = FALSE;
public function getMetaData()
{
$oMetaData = parent::getMetaData();
if (!$this->bMetaDataSet) {
$oMetaData->tableSchema->primaryKey = array( 'field', 'option_value' );
$this->bMetaDataSet = TRUE;
}
return $oMetaData;
}
}
The Yii relation():
class Sites extends CActiveRecord
{
// ...
public function relations()
{
return (
'type_option' => array(
self::BELONGS_TO,
'FieldOptionsDistinct',
array(
'type' => 'option_value',
),
'on' => "type_option.field = 'sites.type'",
),
);
}
}
And all that does the trick. Easy, right?!?

Fat-Free-Framework - Prevent table name from being inserted into mapper object's virtual field

I've been using the Fat-Free Framework recently, and things are going well (arguably better the longer I use it and leverage its components); however, I'm having difficulty with the ORM injecting the MySQL table name into a virtual field (used for lookup).
I know the SQL is good, and I know I could perform a second database call to retrieve the lookup field data, but since I've got things nearly working in virtual field format (and it's probably easiest to digest and debug)...
Is there any way to prevent F3 from inserting the table name during SQL generation?
Setup is easy...
class Bookmark extends \DB\SQL\Mapper
In the constructor, after the call to the parent constructor, I add my virtual fields...
$this->type_name = '
SELECT CASE bookmark_type_id
WHEN 1 THEN \'Project\'
WHEN 2 THEN \'Member\'
ELSE \'Unknown\' END
';
NOTE: This works, though NOT if I use an IF, then I get the table name injected into the IF clause -- after the first comma.
$this->description = '
SELECT CASE bookmark_type_id
WHEN 1 THEN (SELECT p.title FROM projects p WHERE p.id = reference_id)
WHEN 2 THEN (SELECT CONCAT_WS(\' \', m.first_name, m.last_name) FROM members m WHERE m.id = reference_id)
ELSE \'Unknown\' END
';
NOTE: This fails with the table name inserted after the first comma (i.e. before m.first_name).
For clarity, this is the result (notice `cm_bookmark`.):
SELECT CASE bookmark_type_id
WHEN 1 THEN (SELECT p.title FROM projects p WHERE p.id = reference_id)
WHEN 2 THEN (SELECT CONCAT_WS(' ',`cm_bookmark`. m.first_name, m.last_name) AS FullName FROM members m WHERE m.id = reference_id)
ELSE 'Unknown' END
) AS `description`
I get the feeling this is just another one of those "don't do that" situations, but any thoughts on how to achieve this in F3 would be appreciated.
(Oddly, it's only after the first comma in the subquery. If the table name insertion was consistently clever, I'd expect to see it peppered in front of m.last_name too, but it isn't.)
EDIT: It seems as though it's related to the second occurrence of something in parentheses. I've used CONCAT() in another virtual field call, and it works fine -- but it's the first (and only) use of parentheses in the field set up. If I remove the call to CONCAT_WS() and return a single field, the setup above works fine.
EDIT2: To clarify how the load is occurring, see below...
// database setup
// (DB_* set up in config.ini)
$db = new \DB\SQL($f3->get('DB_CONN'), $f3->get('DB_USER'), $f3->get('DB_PASS'));
$f3->set('DB', $db);
...
// Actual call
$db = \Base::instance()->get('DB');
$bookmark = new \CM\Models\Bookmark($db);
$bookmark->load_by_id($item['id']);
...
// in Bookmark Class (i.e. load_by_id)
$b->load(array('id=?', $id));
The only answer (to stay on this path) I have come up with so far is to create another virtual field and piece the 2 parts together later.
Not ideal, but it works.
The mapper does not allow such advanced capabilities, but I would suggest you use Cortex which luckily extends mapper so not much code change.
Below is an example:
Class Bookmark extends \DB\Cortex{
protected
$db = 'DB', // From $f3->set('DB', $db);
$table = 'bookmarks'; // your table name
/* You can also define these custom field preprocessors as a method within the class, named set_* or get_*, where * is the name of your field.*/
public function get_desciption($value) {
switch($this->bookmark_type_id){
case "1":
/*.....................Hope you get the drill*/
}
}
}

Database design: Matching sql database keys to php constants?

Well this is a simple design question I've wondered about many times and never found a satisfying solution for. My example is with php-sql, but this certainly applies to other languages too.
I have a small database table containing only very few entries, and that almost never needs updating. eg this usertype table:
usertype_id (primary key) | name | description
---------------------------+------------+-------------------
1 | 'admin' | 'Administrator'
2 | 'reguser' | 'Registered user'
3 | 'guest' | 'Guest'
Now in the php code, I often have to check or compare the type of user I'm dealing with. Since the user types are stored in the database, I can either:
1) Select * from the usertype table at class instantiation, and store it in an array.
Then all the ids are available to the code, and I can do a simple select to get the rows I need. This solution requires an array and a db query every time the class is instantiated.
$query = "SELECT info, foo FROM user WHERE usertype_id = ".$usertypes['admin'];
2) Use the name column to select the correct usertype_id, so we can effectively join with other tables. This is more or less equivalent to 1) but without needing to cache the whole usertype table in the php object:
$query = "SELECT info, foo FROM user JOIN usertype USING (usertype_id) WHERE usertype.name = 'admin' ";
3) Define constants that match the keys in the usertype table:
// As defines
define("USERTYPE_ADMIN",1);
define("USERTYPE_REGUSER",2);
//Or as class constants
const USERTYPE_ADMIN = 1;
const USERTYPE_REGUSER = 2;
And then do a simple select.
$query = "SELECT info, foo FROM user WHERE usertype_id = " . USERTYPE_ADMIN;
This is probably the most resource-efficient solution, but it is bad to maintain, as you have to update both the table and the code if you need to modify something in the usertype table..
4) Scrap the usertype table and only keep the types in the php code. I don't really like this because it lets any value get into the database and get assigned to the type of user. But maybe, all things considered, it isn't so bad and i'm just complicating something that should be simple..
Anyways, to sum it up the solution I like most is #2 because it's coherent and with an index on usertype.name, it can't be that bad. But what I've often ended up using is #3, for efficiency.
How would you do it? Any better solutions?
(edit: fixed query in #2)
I would suggest #3 to avoid useless queries, and prevent risk of behavior changes if existing DB table rows are incidentally modified:
Adding the necessary constants in the model class:
class Role // + use namespaces if possible
{
// A good ORM could be able to generate it (see #wimvds answer)
const ADMIN = 1;
const USER = 2;
const GUEST = 3;
//...
}
Then querying like this makes sense:
$query = "SELECT info, foo FROM user WHERE role_id = ".Role::ADMIN;
With an ORM (e.g. Propel in the example below) you'll end up doing:
$isAdminResults = UserQuery::create()->filterByRoleId(Role::ADMIN);
I almost always go for option 3). You could generate the code needed automatically based on what is available in the DB. The only thing you have to remember then is that you have to run the script to update/rewrite that info when you add another role (but if you're using phing or a similar build tool to deploy your apps, just add a build rule for it to your deploy script and it will always be run whenever you deploy your code :p).
Why not denormalize the DB table so instead of having usertype_id, you'd have usertype with the string type (admin). Then in PHP you can just do define('USERTYPE_ADMIN', 'admin');. It saves you from having to modify two places if you want to add a user type...
And if you're really worried about any value getting in, you could always make the column an ENUM data type, so it would self manage...
For tables that will contain "type" values especially when is expected such table to change over time I tend to use simple approach:
Add Varchar column named hid (comes from "human readable id") with unique key. Then I fill it with id meaningful to humans like:
usertype_id (primary key) | name | description | hid (unique key)
---------------------------+------------+-------------------+---------------
1 | 'admin' | 'Administrator' | 'admin'
2 | 'reguser' | 'Registered user' | 'user'
3 | 'guest' | 'Guest' | 'guest'
When you need the actual id you will have to do select based on hid column, i.e.
select usertype_id from tablename where hid = "admin"
This is not an efficient approach but it will ensure compatibility of your application among different deployments (i.e. one client may have 1.admin, 2. guest; other client 1.admin, 2. user, etc.). For your case I think #3 is pretty suitable but if you expect to have more than 10 different user roles - try the "hid" approach.
Are you using any kind of framework here? Could these values be stored in a single source - a config file - which both creates a list of the objects in PHP and also populates the table when you bootstrap the database? I'm thinking from a Rails perspective, as it's been a while since I've written any PHP. Solution there would probably be fixtures.
Why not to make it just
foreach (getdbarr("SELECT * FROM usertype") as $row) {
define($row['name'],$row['id']);
}
You shouldn't need a JOIN in every query to fetch the information about types/roles. You can keep your 'user' model and 'role' models separate in the data access objects (DAO) -- especially since there are so few records for user types.
In most cases where I have a limited number of options that I'd otherwise be joining against a large table, I cache them in memcached as an associative array. In the event I need some information about a particular relationship (like a role) I just lazy load it.
$user = DAO_User::get(1); // this pulls a JOIN-less record
$role = $user->getRole(); // lazy-load
The code for $user->getRole() can be something like:
public function getRole() {
// This comes from a cache that may be called multiple
// times per request with no penalty (i.e. store in a registry)
$roles = DAO_UserRoles::getAll();
if(isset($roles[$this->role_id]))
return $roles[$this->role_id];
return null; // or: new Model_UserRole();
}
This also works if you want to display a list with 1000 users on it. You can simply render values for that column from a single $roles associative array.
This is a major performance improvement on the SQL end, and it goes a long way to reducing complexity in your code base. If you have several other foreign keys on the user table you can still use this approach to grab the necessary information when you need it. It also means you can have dependable Model_* classes without having to create hybrids for every possible combination of tables you might JOIN -- which is much better than simply getting a result set, iterating it, and freeing it.
Even with more than 100 rows on both sides of your JOIN, you can still use the lazy load approach for infrequent or highly redundant information. With a reasonable caching service in your code, there's no penalty for calling DAO_UserRole::get(1500) multiple times because subsequent calls during the same request shouldn't hit the database twice. In most cases you're only going to be displaying 10-25 rows per page out of 1000s, and lazy loading will save your database engine from having to JOIN all the extraneous rows before you actually need them.
The main reason to do a JOIN is if your WHERE logic requires it, or if you need to ORDER BY data from a foreign key. Treating JOINs as prohibitively expensive is a good habit to be in.
For basicly static lookup tables, I generally make static constant files (such as your #3). I generally use classes such as:
namespace Constants;
class UserTypes {
const ADMIN = 1;
const USER = 2;
const GUEST = 3;
}
$id = Constants\UserTypes::ADMIN;
When I'm using lookup takes that are a bit more variable, then I'll pull it into a object and then cache it for 24 hours. That way it only gets updated once a day. That will save you from making database round trips, but allow you to deal with things in code easily.
Yeah, you're right about avoiding #3 and sticking with #2. As much as possible, look-ups like when you use a usertype table to contain the roles and then relate them to the user table using the id values should stay in the database. If you use constants, then the data must always rely on your php code to be interpreted. Also, you can enforce data integrity by using foreign keys (where servers allow) and it will allow you to port the reporting from your php code to other reporting tools. Maintenance also becomes easier. Database administrators won't need to know php in order to derive the meanings of the numbers if you used #3, should they ever be asked to aid in reports development. It may not seem too relevant, but in terms of maintenance, using stored procedures than embedded sql in your php code would also be maintenance-friendly in several ways, and will also be advantageous to DBAs.
I'd go for option #2 and use the join as it is intended to be used. You never know what the future will throw up, it's always better to be prepared today!
With regards to leaving the database alone as much as possible for such operations, there is also the possibility of caching in the long term. For this route, within PHP an option is to use a file cache, one that will only get updated when time calls for it. For the framework I have created, here's an example; I'd be interested to know what people think:
Note:
(LStore, LFetch, GetFileName) belong to a Cache object which gets called statically.
(Blobify and Unblobify) belong to a SystemComponent object which is always alive
Each piece of cache data has a key. this is the only thing you ever have to remember
public function LStore($key,$data, $blnBlobify=true) {
/* Opening the file in read/write mode */
$h = fopen(self::GetFileName($key, 'longstore'),'a+');
if (!$h) throw new Exception('Could not write to cache');
flock($h,LOCK_EX); // exclusive lock, will get released when the file is closed
fseek($h,0); // go to the start of the file
/* truncate the file */
ftruncate($h,0);
if($blnBlobify==true) { $data = SystemComponent::Blobify(array($data)); }
If (fwrite($h,$data)===false) {
throw new Exception('Could not write to cache');
}
fclose($h);
}
public function LFetch($key) {
$filename = self::GetFileName($key, 'longstore');
if (!file_exists($filename)){ return false;}
$h = fopen($filename,'r');
if (!$h){ return false;}
/* Getting a shared lock */
flock($h,LOCK_SH);
$data = file_get_contents($filename);
fclose($h);
$data = SystemComponent::Unblobify($data);
if (!$data) {
/* If unserializing somehow didn't work out, we'll delete the file */
unlink($filename);
return false;
}
return $data;
}
/* This function is necessary as the framework scales different directories */
private function GetFileName($key, $strCacheDirectory='') {
if(!empty($strCacheDirectory)){
return SystemComponent::GetCacheAdd() . $strCacheDirectory.'/' . md5($key);
} else {
return SystemComponent::GetCacheAdd() . md5($key);
}
}
public function Blobify($Source){
if(is_array($Source)) { $Source = serialize($Source); }
$strSerialized = base64_encode($Source);
return $strSerialized;
}
public function Unblobify($strSerialized){
$Decoded = base64_decode($strSerialized);
if(self::CheckSerialized($Decoded)) { $Decoded = unserialize($Decoded); }
return $Decoded;
}
function CheckSerialized($Source){
$Data = #unserialize($Source);
if ($Source === 'b:0;' || $Data !== false) {
return true;
} else {
return false;
}
}
Now when it comes to accessing the actual data, I just call a fetch. For making sure it is up to date, I tell it to store. In your case, this would be after updating the usertype table.

Monitor usage of a certain database field

I have a nasty problem. I want to get rid of a certain database field, but I'm not sure in which bits of code it's called. Is there a way to find out where this field is used/called from (except for text searching the code; this is fairly useless seeing as how the field is named 'email')?
Cheers
I would first text search the files for the table name, then only search the tables that contain the table name for the field name.
I wrote a program to do this for my own purposes. It builds an in-memory listing of tables and fields and relates the tables to the fields. Then it loops through tables, searching for the code files that contain the table names, and then searches those files for the fields in the tables found. I'd recommend a similar methodology in your case.
setting mysql to log all queries for some time might help. the queries will give you the tip where to look
brute force - set up a test instance - remove the column - and excercise your test suite.
create a before insert trigger on that table that monitors the insertion on that column.
at the same time create another table called monitor with only one column email
make that table insert the value of NEW.email field into monitor.email as well as in real table.
so you can run your application and check for the existence of any non-null value in monitor table
You should do this in PHP i would expect
For example:
<?php
class Query
{
var $command;
var $resource;
function __construct($sql_command = '')
{
$this->command = $sql_command;
}
public function setResource($resource)
{
$this->resource = $resource;
}
}
//then you would have some kind of database class, but here we would modify the query method.
class Database
{
function query(Query $query)
{
$resource = mysql_query($query->command);
$query->setResource($resource);
//Then you can send the class to the monitor
QueryMonitor::Monitor($query);
}
}
abstract class QueryMonitor
{
public static Monitor(Query $query)
{
//here you use $query->resource to do monitoring of queryies
//You can also parse the query and gather what query type it was:-
//Select or Delete, you can also mark what tables were in the Query
//Even meta data so
$total_found = mysql_num_rows($query->resource);
$field_table = mysql_field_table ($query->resource);
//Just an example..
}
}
?>
Obviously it would be more advanced than that but you can set up a system to monitor every query and every queries meta data in a log file or w.e

PHP Inheritance and MySQL

So I'm trying to adopt good object oriented programming techniques with PHP. Most (read all) of my projects involve a MySQL database. My immediate problem deals with the users model I need to develop.
My current project has Agents and Leads. Both Agents and Leads are Users with much of the same information. So, obviously, I want a class Agents and a class Leads to extend a common class Users. Now, my question is as follows:
How should the SQL best be handled for loading these objects? I don't want to execute multiple SQL statements when I instantiate an Agent or a Lead. However, logic tells me that when the Users constructor is fired, it should execute a SQL statement to load the common information between Agents and Leads (username, password, email, contact information, etc). Logic also tells me that when the Agents or Leads constructor is fired, I want to execute SQL to load the data unique to the Agents or Leads class....But, again, logic also tells me that it's a bad idea to execute 2 SQL statements every time I need an Agent or Lead (as there may be thousands of each).
I've tried searching for examples of how this is generally handled with no success...Perhaps I'm just searching for the wrong thing?
You basically have three approaches to this problem (one of which I'll eliminate immediately):
One table per class (this is the one I'll eliminate);
A record type with optional columns; and
A record type with a child table depending on type that you join to.
For simplicity I generally recommend (2). So once you have your table:
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
type VARCHAR(10),
name VARCHAR(100)
);
where type can be 'AGENT' or 'LEAD' (for example). Alternatively you can use one character type codes. You can then start to fill in the blanks with the object model:
You have a User parent class;
You have two child classes: Lead and Agent;
Those children have a fixed type.
and it should fall into place quite easily.
As for how to load in one statement, I would use some kind of factory. Assuming these barebones classes:
class User {
private $name;
private $type;
protected __construct($query) {
$this->type = $query['type'];
$this->name = $query['name'];
}
...
}
class Agent {
private $agency;
public __construct($query) {
parent::constructor($query);
$this->agency = $query['agency'];
}
...
}
class Lead {
public __consruct($query) {
parent::constructor($query);
}
...
}
a factory could look like this:
public function loadUserById($id) {
$id = mysql_real_escape_string($id); // just in case
$sql = "SELECT * FROM user WHERE id = $id";
$query = mysql_query($sql);
if (!query) {
die("Error executing $sql - " . mysql_error());
}
if ($query['type'] == 'AGENT') {
return new Agent($query);
} else if ($query['type'] == 'LEAD') {
return new Lead($query);
} else {
die("Unknown user type '$query[type]'");
}
}
Alternatively, you could have the factory method be a static method on, say, the User class and/or use a lookup table for the types to classes.
Perhaps polluting the classes with the query result resource like that is a questionable design in the strictest OO sense, but it's simple and it works.
Will you ever have a user that's not a Lead or Agent? Does that class really need to pull data from the database at all?
If it does, why not pull the SQL query into a function you can override when you create the child class.
Could you not inherit say a skeleton of the SQL, then use a function in each sub-class to complete the query based on its needs?
Using a really basic example:
<?php
//our query which could be defined in superclass
$query = "SELECT :field FROM :table WHERE :condition";
//in our subclass
$field = "user, password, email";
$table = "agent";
$condition = "name = 'jim'";
$dbh->prepare($query);
$sth->bindParam(':field', $field);
$sth->bindParam....;//etc
$sth->execute();
?>
As you can see my example isn't amazing, but should allow you to see what I am getting at. If your query is very similar between subclasses then I think my suggestion could work.
Obviously it will need some tweaking but it is probably the approach I would take.

Categories