Switch vs Classes for flow control in PHP - php

Recently, I wrote a simple web application.
I currently have.
A static page which serves the html markup and the resources I need
A javascript routing handling mechanism which communicate with the server and render responses on the client.
For each object I need to manipulate, the server provides a php script at /app/object.php which accepts POST data and return JSON results.
Eg (not actual responses):
POST /app/comments.php?a=add&page_id=43&author=A&text=Some%20Text
{"s":"OK"}
POST /app/comments.php?a=list&page_id=43
[{ "author": 'A', "text": "Some text"}, { "author": "B", "text": "Other text"}]
POST /app/users.php?a=list
["A", "B", "C"]
Under the hood the JSON api is implemented like that:
//comments.php
require('init.php');
// start sessions, open database connections with data in config.php
switch(POST('a')){
case "list":
//.... retrieving data
echo json_encode($data)
break;
case "add":
//.... inserting data
echo '{"s":"OK"}';
break;
}
The largest object has 7 methods and 200 (well-indented, not compressed) LOC, while the average is roughly 3 methods per object.
A developer friend of mine is suggesting to replace the switch with objects, to make it "simpler", "more extensible" and "more maintainable".
I frankly don't see how a system like that could get any simpler (especially by using objects) but I'd love to know other developers' opinions.
Ignoring the performance hit of using objects in PHP, should I use a class-based approach?
If yes, how can I structure my JSON API in order to use objects, without adding too much code (and thus decreasing the maintainability of the project)?

<?php
class Controller {
protected $post;
public function __construct() {
$this->post = $post;
}
public function __call($name, $arguments) {
if(!method_exists($this, $name)) {
die("Unknown action!");
}
}
public function whatever() {
echo json_encode($this->post['page_id']);
}
public function data() {
echo '{"s":"OK"}';
}
// new action? just add another method
}
$controller = new Controller();
$controller->{$_POST('a')}(); // example 1
$controller->data(); // you can't do it using switch
Easy to add new methods
Easy to maintance
You can fire your methods whenever your want
Code is tidy
It's really common practice

Related

Laravel loading relations - framework bug?

I have encountered something strange which I consider might be a bug in the framework itself so I'm wondering if there were similar experiences with it.
I have two entities: Contact and Media in a 1:M relationship. Relationship is defined as:
public function media()
{
return $this->hasMany(Media::class);
}
Now the issue I see is that when I go on show controller method and load the relation like this:
public function show(Contact $contact)
{
$contact->load('media');
return response()->json($contact);
}
The contact is resolved fine, with media relationship in the following way:
"first_name": "Melisa",
...
"media": [
{
"id": 50,
...
However if I modify the case of the relationship so that I call:
public function show(Contact $contact)
{
$contact->load('mEdIa');
return response()->json($contact);
}
It returns:
"first_name": "Melisa",
...
"m_ed_ia": [
{
"id": 50,
...
...which is really disturbing. Is it supposed to happen?
This behaviour should be noted in the documentation of Laravel, which is indeed not the case. If I were you, I would open an issue on updating the documentation about this behaviour, and see what the response will be.
But to come to terms, this is a feature, not a bug.
The nature of PHP is sensitive and insensitive in many ways. For classes and methods; PHP is insensitive.
class Contact {
public function media() {
return .. relationship;
}
}
// valid
new CONTACT();
(new contact())->mediA();
// valid reflection
$r = \ReflectionClass('contact');
$r->hasMethod('mediA'); // true
So this quirky stuff is all valid. Now to the point you said:
.. from my perspective mEdia shouldn't be allowed. My relationship
function name is media, and I want it to be used as such.
Then you have to write it as such. You have to because Laravel depends on it.
PHP is simply unable to validate/verify.
Laravel has to change the nature of a programming language where its sensitivity is deliberately designed by choice.
The expectation here is; you (as a programmer) should know about PHP's sensitivity, so in this case, there is absolutely no need to validate the given input.
The overhead to validate/verify such a thing would be tremendous and would not make sense.
$class = 'contact';
$method = 'mediA';
$r = new \ReflectionClass($class);
if($class !== $r->getName()) {
throw new \Exception('unknown class');
}
if(!in_array($method, array_map(function($method) {
return $method->name;
}, $methods))) {
throw new \Exception('unknown method');
}
// valid from here
From all points of view, the usage of $contacts->with('mediA') is completely valid.
The fact that mediA becomes medi_a using Str::snake('mediA') when converting to an array (as such), is simply a feature of Laravel you have to deal with.
There is such behaviour in Laravel. To get the same key 'mEdIa' - you have to add to media model.
public static $snakeAttributes = false;

Include simple fields based on context in Fractal

I am using the Fractal library to transform a Book object into JSON by using a simple transformer:
class BookTransformer extends \League\Fractal\TransformerAbstract
{
public function transform(Book $book)
{
return [
'name' => $book->getName()
// ...
];
}
}
And I am performing the transformation as follows.
$book = new Book('My Awesome Book');
$resource = new \League\Fractal\Resource\Item($book, new BookTransformer());
$fractal = new \League\Fractal\Manager();
$fractal->setSerializer(new \League\Fractal\Serializer\ArraySerializer());
$json = $fractal->createData($resource)->toJson();
This works great. However, I have certain fields on my Book object that should not always be included, because this depends on the context the transformation is done in. In my particular use case, the JSON returned to AJAX requests from my public website should not include sensitive information, while this should be the case when the data is requested from an admin backend.
So, let's say that a book has a topSecretValue field, which is a string. This field should not be included in one transformation, but should be included in another. I took a look at transformer includes, and played around with it, but this only works with resources. In my case, I need to somehow include different fields (not resources) for different contexts. I have been digging around and could not find anything in the Fractal library that could help me, but maybe I am missing something?
I came up with a working solution, but it is not the prettiest the world has ever seen. By having a BaseBookTransformer that transforms fields that should always be included, I can extend this transformer to add fields for other contexts, e.g. AdminBookTransformer or TopSecretValueBookTransformer, something like the below.
class AdminBookTransformer extends BookTransformer
{
public function transform(Book $book)
{
$arr = parent::transform($book);
$arr['author'] = $book->getTopSecretValue();
return $arr;
}
}
This works fine, although it is not as "clean" as using includes (if it were possible), because I have to actually use a different transformer.
So the question is: is there anything in Fractal that enables me to accomplish this in a simpler/cleaner way, or is there a better way to do it, be it the Fractal way or not?

PHP Handling complex requests through single entry point app

I have been developing a Sports Management website with user, roster, and schedule management using PHP and JS. This app has about 15 database tables to work with altogether. Recently I learned I was doing something majorly wrong: I was using individual PHP scripts as their own "apps".
Example: Individual scripts app
JS
var params = {
action : 'addGame',
date : 'someday',
hometeam : 1,
awayteam : 2
}
$.post('/php/scheduler.php', params);
PHP
class Scheduler extends TableManager{
public function addGame($date, $hometeam, $awayteam){
// do sql stuff
}
public function doAction($request){
switch($request){
case "addGame":
return $this->addGame($_REQUEST['date'], $_REQUEST['hometeam'], $_REQUEST['awayteam'];
break;
}
}
}
if(isset($_REQUEST['action'])){
$scheduler = new Scheduler();
$scheduler->doAction($_REQUEST['action']);
}
I learned that writing a proper PHP application means there should be a single "app" where all requests are routed. This is fine and I've done this - but now I face unknown territory. If I have 15 tables, and they all have a few very specific functions (they all extend TableManager to provide basic table functions, but some are obviously more specific) how can I write a request processor in my main App that handles requests for every table, without getting insanely complex?
Example: Single entry point app
JS
var params = {
action : 'addGame',
table : 'scheduler',
date : 'someday',
hometeam : 1,
awayteam : 2
}
$.post('/php/app.php', params);
PHP
class App {
private $scheduleTable;
public function createTable($name){
switch($name){
case "scheduler":
$this->scheduleTable = new Scheduler();
break;
}
}
public doAction($request){
switch($request){
case "addGame":
$this->createTable('scheduler');
return $this->scheduleTable->addGame($_REQUEST['date'], $_REQUEST['hometeam'], $_REQUEST['awayteam'];
break;
}
}
}
if(isset($_REQUEST['action'])){
$app = new App();
$app ->doAction($_REQUEST['action']);
}
Now I need to start using complex and bloated switch statements, as my tables all inherit TableManager and thus do many of the same actions, but also have unique functions. I'll need to switch on the incoming action, and then switch on the table. Not to mention all of the other features that this App will have (user system, for one). Is the only answer to write a massive switch statement?
From what I understand of your current predicament, yes. You could always use a PHP framework (Google them, there are many) but if you want to use your existing structure then of course you need a switch statement or a way to load the correct models for the page.
Note that you could always use something like this:
<?php
// I assume $request is sanitized.
// Check to see if the class file exists.
if( file_exists( 'class-directory/' . $request . '.inc.php' ) ) {
$class_name = ucfirst( $request );
$this->scheduleTable = new $class_name;
} else {
// Show error page, the request is not valid.
}
?>
Important note: for more security you should use $_POST, $_GET and $_COOKIE instead of $_REQUEST.

How to store data that includes callbacks

There are, of course, many many ways to store a base of data. A database being the most obvious of them. But others include JSON, XML, and so on.
The probem I have with the project I'm working on right now is that the data being stored includes callback functions as part of the objects. Functions cannot be serialised, to my knowledge. So, what should I do?
Is it acceptable to store this data as a PHP file to be included? If so, should I create one big file with everything in, or divide it into separate files for each "row" of the database?
Are there any alternatives that may be better?
Depending on how elaborate you callbacks are, for serializing you could wrap them in all in a class which utilizes some __sleep (create callback representation) & __wakeup (restore callback) voodoo, with an __invoke() method calling the actual callback. Assuming you can reverse engineer / recreate those callbacks (i.e. object to point to is clear).... If not, you are probably out of luck.
Well if they are created by the developer it should be easy to come up with a format... for example with JSON:
{
"callback" : {
"contextType": "instance", // or "static"
"callable" : "phpFunctionName",
"arguments" : []
}
}
So then on models the would be able to use this feature you might do something like:
protected function invokeCallback($json, $context = null) {
$data = json_decode($json, true);
if(isset($data['callaback'])) {
if($data['contextType'] == 'instance') {
$context = is_object($context) ? $context : $this;
$callable = array($context, $data['callable']);
} else {
// data[callable] is already the string function name or a array('class', 'staticMethod')
$callable = $data['callable'];
}
if(is_callable($callable) {
return call_user_func_array($callable, $data['arguments'];
} else {
throw new Exception('Illegal callable');
}
}
return false;
}
Theres some more error handling that needs to happen in there as well as some screening of the callables you want to allow but you get the idea.
Store names of callbacks instead, have a lookup table from names to functions, use table.call(obj, name, ...) or table.apply(obj, name, ...) to invoke. Do not have any code in the serialisable objects themselves. Since they are developer-created, there should be a limited inventory of them, they should be in code, and should not be serialised.
Alternately, make a prototype implementing the callbacks, and assign it to obj.__proto__. Kill the obj.__proto__ just before you serialise - all the functions disappear. Restore it afterwards. Not sure if cross-browser compatible; I seem to have read something about __proto__ not being accessible in some browsers khminternetexploreroperakhm

Optimize this PHP Code?

I am currently working on a mashup that incorporates many data feeds. In order to display ALL of the feeds that the user wants on one page, I am currently using if statements to cross-check with the MySQL database like this:
if($var["type"]=="weather")
$var being the result of a call to mysqli_fetch_array
and then including code relevant to the function (e.g. weather) underneath, and then another "if" statement for another feed, so on so on. The problem is that there will be many feeds, and having all these "if" statements will be slow and redundant.
Is there any way to optimize this PHP code?
Another solution might be to map the "type" to a custom function using associative arrays.
e.g. (pseudo code)
function handle_wheater_logic() {
// ... your code goes here
}
function handle_news_logic() {
// .. your code goes here
}
$customFunctions = array("wheater" => "handle_wheater_logic", "news" => "handle_news_logic");
while ($row = mysql_fetch_...) {
call_user_func ($customFunctions[$row["type"]])
}
This would eliminate the need to use a lot of if statements. You might as well do the "type to function" mapping in a configuration file or maybe just store the name of the custom function to call for each "type" in a database table - that's up to you.
You can, of course also pass parameters to custom function. Just checkout the documentation for call_user_func[_array].
Try this:
$methods = array(
"weather" => function() {
// code
},
"otheroption" => function() {
}
);
Just use then $var["type"] as a index in the array to get the function:
$methods[$var["type"]]();
You can obviuosly, for better readbility do something similar:
$methods = array(
"weather" => "wheater_function",
"otheroption" => "other_function"
);
and then call the functions this way:
call_user_func($methods[$var["type"]]);
To be even more object oriented we can obviously store in the array objects implementing a particular interface, or store object redifining the __call() magic method and use it like functions.
You can use a switch statement.
A good solution for eliminating a lot of if statements and a huge switch statement just checking for one condition, would be to implement a design pattern such as the Strategy pattern.
This way you will have the code for each type separated, which makes it easier to overview and manage.
Here's an example of an implementation http://blogs.microsoft.co.il/blogs/gilf/archive/2009/11/22/applying-strategy-pattern-instead-of-using-switch-statements.aspx
Even if you won't implement this strictly it will give you some ideas on how to solve this elegantly.
Create an array that associates a function to each feed type:
$actions = array("weather" => "getWeather",
"news" => "getNews");
Then use call_user_func to call the correct one:
call_user_func($actions[$var["type"]]);
Polymorphysm for the rescue.
inteface FeedInterface {
public function retrieve($params);
}
class FeedWeather implements FeedInterface {
public function retrieve($params) {
//retrieve logic for weather feed
}
}
class FeedSports implements FeedInterface {
public function retrieve($params) {
//retrieve logic for sports feed
}
}
With use of PHP class autoloading, each of above declarations can be in a separate file, possibly namespaced as well. Then your feed retrieval code could look like this:
$class = 'Feed'.$var["type"];
$feed = new $class;
$feed->retrieve($params);
That's overly simplified and would need some additional code for error handling, discovery of non-existing classes and such, but the idea should be clear.
Using either an If Statement or a Switch statement will be faster than you care about. It might look ugly and be cumbersome to maintain but it will be fast.

Categories