Refactor code to reduce redundancy in PHP - php

I have several classes that share common logic. For example, class AddEmployee is responsible for adding an employee to the api. AddLocation is responsible for adding location of a person. Classes are following :
class AddEmployee
{
public function Add()
{
$apiUrl = "https://api.abc.com/Employees";;
$authParameters = array(
'oauth_consumer_key' => $this->CONSUMER_KEY,
);
$xml .= <<<EOM
<SuperFund>
<ABN>$obj->abn</ABN>
<Type>REGULATED</Type>
</SuperFund>
EOM;
$queryParameters = array(
'xml' => $xml
);
$response = $this->ProcesssRequestAndGetResponse('POST',$apiUrl,$authParameters,$queryParameters,$oauth_secret);
return $response;
}
}
class AddLocation
{
public function Add()
{
$apiUrl = "https://api.abc.com/Locations";;
$authParameters = array(
'oauth_consumer_key' => $this->CONSUMER_KEY,
);
$xml .= <<<EOM
<Location>
<Address1>$obj->abn</Address1>
<City>Dhaka</Citry>
</Location>
EOM;
$queryParameters = array(
'xml' => $xml
);
$response = $this->ProcesssRequestAndGetResponse('POST',$apiUrl,$authParameters,$queryParameters,$oauth_secret);
return $response;
}
}
In the above two classes, only xml part is different and others are same. In future other new classes will be added where only xml will be different.
My question is how can I refactor to remove duplicate from each of the classes ?
Which design pattern to remove duplicate code ?

class AddThing {
public function Add() {//all your stuff, except it calls getXml()}
public abstract function getXml();
}
class AddEmployee extends AddThing {
public function getXml() { // get the employee XML }
}
class AddLocation extends AddThing {
public function getXml() { // get the location XML }
}

In this case: "nevermind 'patterns' ... just do it."
It is quite obvious that you could define a (protected ...) method which takes two parameters: URL and XML. Simply spin-off the bulk of the logic into that method, then change the other (public ...) methods to call it.
Also (and, "IMHO"): "at the end of the day, 'design patterns' are meant to be guidelines." Rules-of-thumb, if you will. Not everything that you encounter in real life will ever exactly conform to a "pattern," nor do you need to feel that "you must find one" to justify what you, as an engineer, decide to do.
Instead, think very-pragmatically about the application, the work-group, and the changes that you anticipate being necessary in the future. Try to design methods in such a way that your successors, when faced with an inevitable future change, might not have to change "a hundred methods." (Instead, thanks to your foresight, they need only change a few.) Use your best judgment, after discussing the matter carefully with your manager and with other members of your team.

Related

How to ignore specific private static function with PHPUnit?

I'm new to PHPUnit and wondering is it possible to write a test for which ignore specific method.
The code is like examine whether $data is Valid or not, and if it find irregular data, send message to slack with it.
My question is, is it possible to run a test without sending alert message, like ignore sendAlert function?
If possible, I want to know how to write it, If not, I want know why and how to make this code testable.
Thanks!!
example code )
public static function isValid($data) {
// some code here
if (Valid) {
return true;
} else {
// some code here to find irregular
if (irregular) {
self::sendAlert($data);
}
return false;
}
}
private static function sendAlert($data) {
// send alert to slack
Example_Model_Slack::post($slackMsg, $channel);
}
<?
class Example_Model_Slack
{
public static function post($text, $channel = '') {
// make $params from $text and $channel
// POST
$stream = [
'http' => [
'method' => 'POST',
'protocol_version' => 1.1,
'content' => http_build_query($params),
],
];
return file_get_contents(self::POST_URL, false, stream_context_create($stream));
}
}
Edit after the question edit
If your code is in a namespace (which should be, it's good practice), it's extremely easy:
Create a new function in a separate file that is only included by your UnitTest file. This file should have the same namespace as your code. In this example, Example_Model_Slack is in the namespace Foobar\Models.
<?php
namespace Foobar\Models;
function file_get_contents(string $filename, bool $use_include_path = false, resource $context = ?)
{
return 'Whatever you want';
}
When you call a function, the code looks for it:
In the specifically used functions.
In the same namespace.
In the built-in functions.
Therefore, your code will use the built-in file_get_contents (namely \file_get_contents), but your test will use the one in the same namespace (namely \Foobar\Models\file_get_contents).
Original answer
The easiest would be to actually call sendAlert, but to mock the call to its content. As you didn't provide the code of that method, I can't be more precise, juste browse through the doc and figure it out by yourself or, alternatively, show us the code.
For a theorectical and general answer: your sendAlert method probably uses one that is provided by an external vendor, let's say \SlackApi\Slack::send($message). In that case, you could mock the provided \SlackApi\Slack class to replace the send method with one that doesn't actually send anything but still returns the expected data.

Reducing manual object instantiation

I am trying to learn to the Dependency Inversion Principle. Currently my code is like this
class Example {
public function __construct( $input, $output ) {
$input_handler = new InputHandler( $input );
$output_handler = new OutputHandler( $output );
$input_handler->doStuff();
$output_handler->doOtherStuff();
}
}
$input = new Input();
$output = new Output();
$example = new Example( $input, $output)
However, it seems using basic dependency injection, it should be more like this?
class Example {
public function __construct( $input_handler, $output_handler ) {
$input_handler->doStuff();
$output_handler->doOtherStuff();
}
}
$input = new Input();
$output = new Output();
$input_handler = new InputHandler( $input );
$output_handler = new OutputHandler( $output);
$example = new Example( $input_handler, $output_handler)
Is this is correct?
I want to let the programmer choose the type of input / output to use when running the program. So with dependency injection (as far as I understand) it would look like this;
$input = new ConsoleInput();
$output = new FileOutput();
$input_handler = new ConsoleInputHandler( $input );
$output_handler = new FileOutputHandler( $output);
$example = new Example( $input_handler, $output_handler);
$example->doStuffToOutput();
However, I would prefer to make the programmers life a little easier by only needing to pass in the type of input and output, and not need to worry about the classes handling them;
$input = new ConsoleInput();
$output = new FileOutput();
$example = new Example( $input, $output );
$example->doStuffToOutput();
or even
$example = new Example( new ConsoleInput(), new FileOutput() );
$example->doStuffToOutput();
How can I achieve this using DIP and not end up with my initial code block? Is this a good thing to do?
While I was reading your question I felt you have two main goals. Firstly to improve the readability of your code ('..ease the programmer's life') and secondly to decouple "Example" class from the I/O handlers. For my point of view, DI is just a principle to follow in order to reach your goals.
Before I'm attaching any code, I want to emphasize that sometimes it is better to actually couple your code. Code must be coupled somehow. Do not use DI everywhere just because it has been said. Simplicity, as being described with the KISS and YAGNI principles, is always the winner.
So the big question here is whether your second goal (decoupling with DI) is the smart thing to do. Is there a real reason for the InputHandler / OutputHandler in the "Exmaple" class to be changed? If "No" is your answer, I would recommend you to keep it inside this class intact. And "maybe in the distant future it will be profitable" doesn't really count.
However, if your handlers should be unique for each type (file, console etc.), and your decoupling would help you and other programmers to extend the platform, you can take advantage of the Factory pattern. You have several ways to implement this pattern (static / abstract / simple / method factories). The main goal is to lessen the learning curve from the client, and make the "Example" class decoupled, so that adding more types or handlers would not affect this class.
class HandlerFactory {
protected static function createInputHandler(Input $input)
{
switch ($input)
{
case is_a($input, 'FileInput'):
return new FileInputHandler($input);
case is_a($input, 'ConsoleInput'):
return new ConsoleInputHandler($input);
}
throw new \Exception('Missing Input handler');
}
protected static function createOutputHandler(Output $output)
{
switch ($output)
{
case is_a($output, 'FileOutput'):
return new FileOutputHandler($output);
case is_a($output, 'ConsoleOutput'):
return new ConsoleOutputHandler($output);
}
throw new \Exception('Missing Output handler');
}
public static function createHandler($io)
{
switch ($io)
{
case is_a($io, 'Input'):
return self::createInputHandler($io);
case is_a($io, 'Output'):
return self::createOutputHandler($io);
}
throw new \Exception('Missing I/O handler');
}
}
Now your first code in your question is still relevant with a minor twist:
class Example {
public function __construct($input, $output) {
$input_handler = HandlerFactory::createHandler($input);
$output_handler = HandlerFactory::createHandler($output);
$input_handler->doStuff();
$output_handler->doOtherStuff();
}
}
$input = new Input();
$output = new Output();
$example = new Example($input, $output);
Use an Abstract Factory class to handle the instantiation of objects needed to handle i/o. You could either inject the factory into the example class, or let the factory instantiate the objects needed and then inject those into the example class. Then you can do:
$IOFactory = new IOFactory();
$example = new Example($IOFactory::makeInputHandler($inputType), $IOFactory::makeOutputHandler($outputType));
$example->doStuffToOutput();
IOFactory takes care of instantiating input and output objects base in their specific types, and then instantiates the handlers and inject them with the input and output object. Afterwards returns the handler objects to be injected in the example object.
In your case you can choose one of many creational design patterns available. My suggestion is to go with either Factory pattern or object pool pattern.
In case of factory method pattern, you can have a class with the responsibility of creating object:
class ObjectFactory {
public InputHandler createInputHandlerObject(inputobj){
if( inputobj instanceOf ConsoleInput ) {
return new ConsoleInputHandler();
} else if( inputobj instanceOf FileInput ) {
}
}
// similarly create a method for creating OutputHandler object.
//create the appropriate object by using instanceOf operator.
Since I'm familiar with Java i have given example in Java . you can change the syntax and use accordingly. This is not the only way to implement Factory Pattern.
If you want to remove the burden of creating the object at runtime you can use Object pool pattern. Hybrid of prototype pattern also becomes handy in your csse.

PHP Soap Server How to know called method

In PHP I would like to know what will be the method called by SOAP. Here is a sample to understand...
$soapserver = new SoapServer();
$soapserver->setClass('myClass');
$soapserver->handle();
What I would like to know is the name of the method that will be executed in handle()
Thank you !!
In my opinion, the cleanest and most elegant way to access the called operation's name in this situation would be using some kind of Wrapper or Surrogate design pattern. Depending on Your intent You would use either the Decorator or the Proxy.
As an example, let's say We want to dynamically add some additional functionality to our Handler object without touching the class itself. This allows for keeping the Handler class cleaner and, thus, more focused on its direct responsibility. Such functionality could be logging of methods and their parameters or implementing some kind of caching mechanism. For this We will use the Decorator design pattern. Instead of doing this:
class MyHandlerClass
{
public function operation1($params)
{
// does some stuff here
}
public function operation2($params)
{
// does some other stuff here
}
}
$soapserver = new SoapServer(null, array('uri' => "http://test-uri/"));
$soapserver->setClass('MyHandlerClass');
$soapserver->handle();
We'll do the following:
class MyHandlerClassDecorator
{
private $decorated = null;
public function __construct(MyHandlerClass $decorated)
{
$this->decorated = $decorated;
}
public function __call($method, $params)
{
// do something with the $method and $params
// then call the real $method
if (method_exists($this->decorated, $method)) {
return call_user_func_array(
array($this->decorated, $method), $params);
} else {
throw new BadMethodCallException();
}
}
}
$soapserver = new SoapServer(null, array('uri' => "http://test-uri/"));
$soapserver->setObject(new MyHandlerClassDecorator(new MyHandlerClass()));
$soapserver->handle();
If You want to control the access to the Handler's operations, for instance, in order to impose access rights use the Proxy design pattern.
I know this is an old post, but someone could make use of this solution. It should be possible to extract data from raw HTTP POST data. You cannot use $_POST, because it's empty but you can use predefined variable $HTTP_RAW_POST_DATA which contains string with SOAP request in XML format.
The method name should be in first node of <soapenv:Body> tag like this:
<!--
...
XML header, SOAP header etc.
...
-->
<soapenv:Body>
<urn:methodName soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<param1 xsi:type="xsd:string" xs:type="type:string" xmlns:xs="http://www.w3.org/2000/XMLSchema-instance">param1 value</param1>
<param2 xsi:type="xsd:string" xs:type="type:string" xmlns:xs="http://www.w3.org/2000/XMLSchema-instance">param2 value</param2>
</urn:methodName>
</soapenv:Body>
<!--
...
-->
You could probably parse it with something like SimpleXML or maybe use some regular expresion to get methodName but remember that the string urn: is a namespace defined in the header and therefore it could be anything.
Although not the nicest way, you can use this http://danpolant.com/use-the-output-buffer-to-debug-a-soap-server/ somehow.
For the quick and very dirty approach (please use this only for a one-time debug and not in production code!): just assign a global variable with the name of each SOAP method in the method bodies and do whatever you want with it after the SoapServer does its job, as described in the above link. Something like this (untested code):
$method = "";
class test
{
function call1()
{
global $method; $method = "call1";
}
}
ob_start();
$soapserver = new SoapServer();
$soapserver->setClass('test');
$soapserver->handle();
$mystring = ob_get_contents(); // retrieve all output thus far
ob_end_clean (); // stop buffering
log($mystring); // log output
log($method); // log method
echo $mystring; // now send it
Usually (but not always, depends on the client) $_SERVER['HTTP_SOAPACTION'] is set and you can get the name of the called method from it.

Does this MVC controller code need to be refactored or not?

I am writing an CSV/Excel-->MySQL import manager for an MVC application (Kohana/PHP).
I have a controller named "ImportManager" which has an action named "index" (default) which displays in a grid all the valid .csv and .xls files that are in a specific directory and ready for import. The user can then choose the files he wants to import.
However, since .csv files import into one database table and .xls files import into multiple database tables, I needed to handle this abstraction. Hence I created a helper class called SmartImportFile to which I send each file be it .csv or .xls and then I get then ask this "smart" object to add the worksheets from that file (be they one or many) to my collection. Here is my action method in PHP code:
public function action_index()
{
$view = new View('backend/application/importmanager');
$smart_worksheets = array();
$raw_files = glob('/data/import/*.*');
if (count($raw_files) > 0)
{
foreach ($raw_files as $raw_file)
{
$smart_import_file = new Backend_Application_Smartimportfile($raw_file);
$smart_worksheets = $smart_import_file->add_smart_worksheets_to($smart_worksheets);
}
}
$view->set('smart_worksheets', $smart_worksheets);
$this->request->response = $view;
}
The SmartImportFile class looks like this:
class Backend_Application_Smartimportfile
{
protected $file_name;
protected $file_extension;
protected $file_size;
protected $when_file_copied;
protected $file_name_without_extension;
protected $path_info;
protected $current_smart_worksheet = array();
protected $smart_worksheets = array();
public function __construct($file_name)
{
$this->file_name = $file_name;
$this->file_name_without_extension = current(explode('.', basename($this->file_name)));
$this->path_info = pathinfo($this->file_name);
$this->when_file_copied = date('Y-m-d H:i:s', filectime($this->file_name));
$this->file_extension = strtolower($this->path_info['extension']);
$this->file_extension = strtolower(pathinfo($this->file_name, PATHINFO_EXTENSION));
if(in_array($this->file_extension, array('csv','xls','xlsx')))
{
$this->current_smart_worksheet = array();
$this->process_file();
}
}
private function process_file()
{
$this->file_size = filesize($this->file_name);
if(in_array($this->file_extension, array('xls','xlsx')))
{
if($this->file_size < 4000000)
{
$this->process_all_worksheets_of_excel_file();
}
}
else if($this->file_extension == 'csv')
{
$this->process_csv_file();
}
}
private function process_all_worksheets_of_excel_file()
{
$worksheet_names = Import_Driver_Excel::get_worksheet_names_as_array($this->file_name);
if (count($worksheet_names) > 0)
{
foreach ($worksheet_names as $worksheet_name)
{
$this->current_smart_worksheet['name'] = basename($this->file_name).' ('.$worksheet_name.')';
$this->current_smart_worksheet['kind'] = strtoupper($this->file_extension);
$this->current_smart_worksheet['file_size'] = $this->file_size;
$this->current_smart_worksheet['when_file_copied'] = $this->when_file_copied;
$this->current_smart_worksheet['table_name'] = $this->file_name_without_extension.'__'.$worksheet_name;
$this->assign_database_table_fields();
$this->smart_worksheets[] = $this->current_smart_worksheet;
}
}
}
private function process_csv_file()
{
$this->current_smart_worksheet['name'] = basename($this->file_name);
$this->current_smart_worksheet['kind'] = strtoupper($this->file_extension);
$this->current_smart_worksheet['file_size'] = $this->file_size;
$this->current_smart_worksheet['when_file_copied'] = $this->when_file_copied;
$this->current_smart_worksheet['table_name'] = $this->file_name_without_extension;
$this->assign_database_table_fields();
$this->smart_worksheets[] = $this->current_smart_worksheet;
}
private function assign_database_table_fields()
{
$db = Database::instance('import_excel');
$sql = "SHOW TABLE STATUS WHERE name = '".$this->current_smart_worksheet['table_name']."'";
$result = $db->query(Database::SELECT, $sql, FALSE)->as_array();
if(count($result))
{
$when_table_created = $result[0]['Create_time'];
$when_file_copied_as_date = strtotime($this->when_file_copied);
$when_table_created_as_date = strtotime($when_table_created);
if($when_file_copied_as_date > $when_table_created_as_date)
{
$this->current_smart_worksheet['status'] = 'backend.application.import.status.needtoreimport';
}
else
{
$this->current_smart_worksheet['status'] = 'backend.application.import.status.isuptodate';
}
$this->current_smart_worksheet['when_table_created'] = $when_table_created;
}
else
{
$this->current_smart_worksheet['when_table_created'] = 'backend.application.import.status.tabledoesnotexist';
$this->current_smart_worksheet['status'] = 'backend.application.import.status.needtoimport';
}
}
public function add_smart_worksheets_to(Array $smart_worksheets = array())
{
return array_merge($smart_worksheets, $this->get_smart_worksheets());
}
public function get_smart_worksheets()
{
if ( ! is_array($this->smart_worksheets))
{
return array();
}
return $this->smart_worksheets;
}
}
In a code review I was told that it might be better not to have a helper class like this but to keep the bulk of the code in the controller action method itself. The argumentation was that you should be able to look at the code in a controller action and see what it does instead of having it call external helper classes outside of itself. I disagree. My argumentation is:
you should create a helper class anytime it makes code clearer, as in this case, it abstracts away the fact that some files have one worksheet or many worksheets in them, and allows for easy future extension, if, say, we want to also import from sqlite files or even directories with files in them, this class abstraction would be able to handle this nicely.
moving the bulk of the code from this helper class back into the controller would force me to create internal variables in the controller which make sense for this particular action, but may or may not make sense to other action methods within the controller.
if I were programming this in C# I would make this helper class a nested class which would literally be an internal data structure that is inside of and only available to the controller class, but since PHP does not allow nested classes, I need to call a class "outside" the controller to help manage this abstraction in a way that makes the code clear and readable
Based on your experience of programming in the MVC pattern, should the above helper class be refactored back into the controller or not?
There are two approaches to controllers: make it thin or thick. When I started my adventure with MVC I made a mistake of creating thick controllers - now I prefer make it as thin as possible. Your solution is good in my opinion.
Here is how I would redesigned your code even further:
class Backend_Application_SmartImport {
public function __construct( $raw_files ) {
}
public function process() {
foreach ($raw_files as $raw_file) {
// (...)
$oSmartImportFileInstance = $this->getSmartImportFileInstance( $smart_import_file_extension );
}
}
protected function getSmartImportFileInstance( $smart_import_file_extension ) {
switch ( $smart_import_file_extension ) {
case 'xml':
return new Backend_Application_SmartImportFileXml();
// (...)
}
}
}
abstract class Backend_Application_SmartImportFile {
// common methods for importing from xml or cvs
abstract function process();
}
class Backend_Application_SmartImportFileCVS extends Backend_Application_SmartImportFile {
// methods specified for cvs importing
}
class Backend_Application_SmartImportFileXls extends Backend_Application_SmartImportFile {
// methods specified for xls importing
}
The idea is to have two classes responsible for processing xml and cvs inheriting from a base class. The main class uses a special method to detect how the data should be processed (Strategy Pattern). The controller just passed a list of files to the instance of Backend_Application_SmartImport class and passes result of process method to the view.
The advantage of my solution is that code is more decoupled and you can easily and in a clean way add new types of processing like xml, pdf, etc.
I agree with you Edward.
Your ImportController does what a Controller is meant to do. It generates the list of files for the user to view and act on, it then passes that list to the View for it to display. I am presuming that you have a process action or similar which is handles the request when a user has selected a file, this file is then passed on to the Helper in question.
The Helper is a perfect example of abstraction and entirely justified in its usage and existence. It is not coupled with the Controller in anyway and doesn't need to be. The Helper could be easily used in other scenarios where the Controller is not present, for example a CRON task, a public API which users can call programmatically without your ImportController.
Your right on the ball with this one. Stick it to 'em!

Problems with redeclaring classes when using PHP's class_alias in a functional test

I'm using PHP 5.3's class_alias to help process my Symfony 1.4 (Doctrine) forms. I use a single action to process multiple form pages but using a switch statement to choose a Form Class to use.
public function executeEdit(sfWebRequest $request) {
switch($request->getParameter('page')) {
case 'page-1':
class_alias('MyFormPage1Form', 'FormAlias');
break;
...
}
$this->form = new FormAlias($obj);
}
This works brilliantly when browsing the website, but fails my functional tests, because when a page is loaded more than once, like so:
$browser->info('1 - Edit Form Page 1')->
get('/myforms/edit')->
with('response')->begin()->
isStatusCode(200)->
end()->
get('/myforms/edit')->
with('response')->begin()->
isStatusCode(200)->
end();
I get a 500 response to the second request, with the following error:
last request threw an uncaught exception RuntimeException: PHP sent a warning error at /.../apps/frontend/modules/.../actions/actions.class.php line 225 (Cannot redeclare class FormAlias)
This makes it very hard to test form submissions (which typically post back to themselves).
Presumably this is because Symfony's tester hasn't cleared the throughput in the same way.
Is there a way to 'unalias' or otherwise allow this sort of redeclaration?
As an alternate solution you can assign the name of the class to instantiate to a variable and new that:
public function executeEdit(sfWebRequest $request) {
$formType;
switch($request->getParameter('page')) {
case 'page-1':
$formType = 'MyFormPage1Form';
break;
...
}
$this->form = new $formType();
}
This doesn't use class_alias but keeps the instantiation in a single spot.
I do not know for sure if it is possible, but judging from the Manual, I'd say no. Once the class is aliased, there is no way to reset it or redeclare it with a different name. But then again, why do use the alias at all?
From your code I assume you are doing the aliasing in each additional case block. But if so, you can just as well simply instantiate the form in those blocks, e.g.
public function executeEdit(sfWebRequest $request) {
switch($request->getParameter('page')) {
case 'page-1':
$form = new MyFormPage1Form($obj);
break;
...
}
$this->form = $form;
}
You are hardcoding the class names into the switch/case block anyway when using class_alias. There is no advantage in using it. If you wanted to do it dynamically, you could create an array mapping from 'page' to 'className' and then simply lookup the appropriate class.
public function executeEdit(sfWebRequest $request) {
$mapping = array(
'page-1' => 'MyFormPage1Form',
// more mappings
);
$form = NULL;
$id = $request->getParameter('page');
if(array_key_exists($id, $mapping)) {
$className = $mapping[$id];
$form = new $className($obj);
}
$this->form = $form;
}
This way, you could also put the entire mapping in a config file. Or you could create FormFactory.
public function executeEdit(sfWebRequest $request) {
$this->form = FormFactory::create($request->getParameter('page'), $obj);
}
If you are using the Symfony Components DI Container, you could also get rid of the hard coded factory dependency and just use the service container to get the form. That would be the cleanest approach IMO. Basically, using class_alias just feels inappropriate here to me.
function class_alias_once($class, $alias) {
if (!class_exists($alias)) {
class_alias($class, $alias);
}
}
This doesn't solve the problem itself, but by using this function it is ensured that you don't get the error. Maybe this will suffice for your purpose.

Categories