5
\$\begingroup\$

I have made custom basic MVC using index-php. I have a very long add function which does alot of things. sanitisation, validating, more checking data etc.. finally input new records. Sometimes the add can be huge so what i have done is create a background task which does some of this at a later time run independently from web service. How can i make this more reusable or easier to understand what is happening other thaa put comments and or optimise to make faster

function in controller

public function add() { $request = $this->requestData; $method = 'request_exemption'; $request->set('method', $method); $request->set('quiet', 1); $this->responseData = ''; $this->dbdataCount = false; global $CFG; $method = new Method($method, [], [], $request); $response = $method->getResponse(); if (! $response['error']) { $exemption_type = $request->get('exemption_type'); $market_id = (int) $request->get('market'); $service_domain = $request->get('service_domain'); $service_id = ($request->get('service_id') != '') ? (int) $request->get('service_id') : ''; $demand_ref = $request->get('demand_ref'); $name = $request->get('name'); $description = $request->get('description'); $comms_matrix_id = ($request->get('comms_matrix_id') != '') ? (int) $request->get('comms_matrix_id') : ''; $requestor = $request->get('requestor'); $business_priority = $request->get('business_priority'); $expiry_date = $request->get('expiry_date'); $designdoc = $request->get('designdoc'); $known_contact = $request->get('known_contact'); $on_behalf_of = ($request->get('on_behalf_of') !== null) ? $request->get('on_behalf_of') : ''; if ($service_id === 0) { // null for global exemption $service_id = null; } if ($comms_matrix_id === 0) { // null for global exemption $comms_matrix_id = null; } if (! empty($comms_matrix_id)) { $user = new User(); $sysprivusers = new SysPrivUsers(); if ($sysprivusers->isSystemUser($user->getUsername())) { $isMemberOrUploaded = $user->isMemberOrUploaded((int) $comms_matrix_id); if ($isMemberOrUploaded['uploaded_by'] === null && $isMemberOrUploaded['member_of'] === null) { $response['error'] = true; $response['message'] = 'You are not authorised'; $this->log->error($isMemberOrUploaded); $this->log->error($user->getUsername()); foreach ($response as $key => $value) { $this->{$key} = $value; unset($response[$key]); } return $this; } } } else { $user = new User(); if(empty($service_id) && empty($comms_matrix_id)){ if (!$user->isSecArchUser()) { $response['error'] = true; $response['message'] = 'You are not authorised'; $this->log->error("isNotSecArch:" . $user->getUsername() . " : " . $user->isSecArchUser()); foreach ($response as $key => $value) { $this->{$key} = $value; unset($response[$key]); } return $this; } } if ($user->isLocalMarket()) { $response['error'] = true; $response['message'] = 'You are not authorised'; $this->log->error("isLocalMarket:" . $user->getUsername() . " : " . $user->isLocalMarket()); foreach ($response as $key => $value) { $this->{$key} = $value; unset($response[$key]); } return $this; } } $validate = new Validation(); $params = [ 'exemption_type' => $exemption_type, 'market_id' => $market_id, 'service_domain' => $service_domain, 'demand_ref' => $demand_ref, 'exemption_name' => $name, 'exemption_description' => $description, 'requestor' => $requestor, 'business_priority' => $business_priority, 'expiry_date' => $expiry_date, 'designdoc' => $designdoc, 'known_contact' => $known_contact ]; if ($service_id) { // global and service exemptions will have a null service id $params['service_id'] = $service_id; } if ($comms_matrix_id) { // global and service exemptions will have a null comms matrix id $params['comms_matrix_id'] = $comms_matrix_id; } if (isset($params['comms_matrix_id'])) { $params['comms_matrix_id_exemptions'] = $params['comms_matrix_id']; unset($params['comms_matrix_id']); } if (isset($params['service_id'])) { $params['service_id_exemptions'] = $params['service_id']; unset($params['service_id']); } if (isset($params['description'])) { $params['exemption_description'] = $params['description']; unset($params['description']); } $valid = $validate->validateForm($params); if (isset($params['comms_matrix_id_exemptions'])) { $params['comms_matrix_id'] = $params['comms_matrix_id_exemptions']; unset($params['comms_matrix_id_exemptions']); } if (isset($params['service_id_exemptions'])) { $params['service_id'] = $params['service_id_exemptions']; unset($params['service_id_exemptions']); } if (isset($params['exemption_description'])) { $params['description'] = $params['exemption_description']; unset($params['exemption_description']); } // add notify users if ($on_behalf_of != '') { $emails = preg_split('/[;, ]\s{0,1}/', $on_behalf_of); foreach ($emails as $email) { $email = trim($email); // check if email is valid in ldap $ldap = new LDAP(); $connect = $ldap->ldapConnect($CFG->ldap_host, $CFG->adsvcuser); $filter = "(mail=$email)"; $read = ldap_search($connect, $CFG->ldap_basedn, $filter, array( "mail" )) or header('Location: /error.html'); $entries = ldap_get_entries($connect, $read); if ($entries['count'] === 0) { $response['error'] = true; $valid .= "Email not found - {$email}"; } } } // check if no worker jobs processing before allowing user to create exemption if (empty($valid) && ! empty($comms_matrix_id) && $comms_matrix_id > 0) { $workerJobs = new WorkerJobs(); $count = $workerJobs->fetchAllArray(null, [ 'job' => [ 'IN', [ 'routingAnalysisNitx', 'reAnalyze', 'aggregateFlows', 'securityExemptionApprove' ] ], 'flag' => [ 'IN', [ 'Q', 'P' ] ], 'arg1' => $comms_matrix_id ]); if (count($count) > 0) { $valid = "CM is currently processing"; $this->log->error($count); } } if(!empty($valid)){ $response['error'] = true; $response['message'] = $valid; $this->log->error($valid . " : $comms_matrix_id"); }else{ $params['name'] = $name; $params['description'] = $description; $db = new DatabaseConnection(); //look up ids $sql = "SELECT id FROM ".DBUSER.".service_domains WHERE TRIM(name) = :name"; $binds = [':name' => [$service_domain,50,SQLT_CHR]]; $service_domain_id = $db->executeToSingleCell($sql,$binds); $sql = "SELECT id FROM ".DBUSER.".exemption_types WHERE TRIM(name) = :name"; $binds = [':name' => [$exemption_type,32,SQLT_CHR]]; $exemption_type_id = $db->executeToSingleCell($sql,$binds); $sql = "SELECT id FROM ".DBUSER.".sys_list_business_priority WHERE TRIM(name) = :name"; $binds = [':name' => [$business_priority,20,SQLT_CHR]]; $business_priority_id = $db->executeToSingleCell($sql,$binds); $exemption_status = 'Requested'; $sql = "SELECT id, name FROM ".DBUSER.".sys_list_exemption_status WHERE TRIM(name) = :name"; $binds = [':name' => [$exemption_status,20,SQLT_CHR]]; $exemption_status_id = $db->executeToSingleCell($sql,$binds); $data = [ 'service_id' => $service_id, 'comms_matrix_id' => $comms_matrix_id, "exemption_type_id" => $exemption_type_id, "market_id" => $market_id, "service_domain_id" => $service_domain_id, "demand_ref" => $demand_ref, "name" => $name, "description" => $description, "requestor" => $requestor, "business_priority_id" => $business_priority_id, "exemption_status_id" => $exemption_status_id, "expiry_date" => $expiry_date, "hits" => 0, "designdoc" => $designdoc, "known_contact" => $known_contact ]; if(isset($params['comms_matrix_id'])){ $params['comms_matrix_id_exemptions'] = $params['comms_matrix_id']; unset($params['comms_matrix_id']); } if(isset($params['service_id'])){ $params['service_id_exemptions'] = $params['service_id']; unset($params['service_id']); } if(isset($params['name'])){ $params['exemption_name'] = $params['name']; unset($params['name']); } if(isset($params['description'])){ $params['exemption_description'] = $params['description']; unset($params['description']); } $valid = $validate->validateForm($params); if(isset($params['comms_matrix_id_exemptions'])){ $params['comms_matrix_id'] = $params['comms_matrix_id_exemptions']; unset($params['comms_matrix_id_exemptions']); } if(isset($params['service_id_exemptions'])){ $params['service_id'] = $params['service_id_exemptions']; unset($params['service_id_exemptions']); } if(isset($params['exemption_name'])){ $params['name'] = $params['exemption_name']; unset($params['exemption_name']); } if(isset($params['exemption_description'])){ $params['description'] = $params['exemption_description']; unset($params['exemption_description']); } if(!empty($valid)){ $response['error'] = true; $response['message'] = $valid; $this->log->error($response); }else{ $model = new Exemptions(); unset($data["description"]);//$this->log->debug($data); $dbData = $model->fetchAllArray(['COUNT(*) COUNT'],$data); if((int)$dbData[0]['count'] > 0){ $response ['error'] = true; $response ['message'] = "Entry exists"; $this->log->error($response); }else{ if($data['comms_matrix_id'] > 0){ //check to make sure there are denied flows $vCommsMatrices = new VCommsMatrices($data['comms_matrix_id']); $cmd = new CommsMatrixData(); $countCMD = $cmd->fetchAllArray(['COUNT(*) COUNT'], [ 'comms_matrix_id'=>$data['comms_matrix_id'], 'spa'=> ['OR',[['IN',['Deny'],'spa'],['IN',['Deny'],'fpa']]], "trunc(ts_created,'hh24')"=>[ ['>=',"trunc(to_date('".$vCommsMatrices->fieldVals['TS_CREATED']."','dd-Mon-YY HH24:MI:SS') ,'hh24')"], ['<=',"trunc(to_date('".$vCommsMatrices->fieldVals['TS_UPDATED']."','dd-Mon-YY HH24:MI:SS') ,'hh24')"] ] ]); if((int)$countCMD[0]['count'] == 0){ $this->error = true; $this->message = "There are no deny flows to raise exemption on for this comms matrix"; return $this; } //check to make sure there are no worker jobs or re analysing $countCMD = $cmd->fetchAllArray(['COUNT(*) COUNT'],['comms_matrix_id'=>$data['comms_matrix_id'], 'spa'=> ['OR',[['IN',['Pending'],'spa'],['IN',['Pending'],'fpa']]] ]); if((int)$countCMD[0]['count'] > 0){ $this->error = true; $this->message = "The comms matrix is being re analysed. Please wait for re analyse to complete."; return $this; } // secondary check to see if any exemptions exist with same amoutn of flows $dbData = $model->fetchAllArray(null,['comms_matrix_id'=>$data['comms_matrix_id'],'exemption_status_id'=>['IN',[1]]]); $countDbData = count($dbData); if($countDbData > 0){ for($i=0;$i<$countDbData; $i++){ $exemptionPolicies = new ExemptionPolicies(); $countData = $exemptionPolicies->fetchAllArray(['COUNT(*) COUNT'],['exemption_id'=>$dbData[$i]['id']]); $vCommsMatrices = new VCommsMatrices($data['comms_matrix_id']); $cmd = new CommsMatrixData(); $countCMD = $cmd->fetchAllArray(['COUNT(*) COUNT'], [ 'comms_matrix_id'=>$data['comms_matrix_id'], 'spa'=> ['OR',[['IN',['Deny'],'spa'],['IN',['Deny'],'fpa']]] , "trunc(ts_created,'hh24')"=>[ ['>=',"trunc(to_date('".$vCommsMatrices->fieldVals['TS_CREATED']."','dd-Mon-YY HH24:MI:SS') ,'hh24')"], ['<=',"trunc(to_date('".$vCommsMatrices->fieldVals['TS_UPDATED']."','dd-Mon-YY HH24:MI:SS') ,'hh24')"] ] ]); if((int)$countData[0]['count'] == (int)$countCMD[0]['count']){ $this->error = true; $this->message = "Exemption for this comms matrix already exists. ({$dbData[$i]['id']})"; return $this; } } } } $data["description"] = $description; if(isset($data['expiry_date']) && $data['expiry_date']){ $date = \DateTime::createFromFormat('d-M-y', $data['expiry_date']); $data['expiry_date'] = $date->format('Y-m-d H:i:s'); } $result = $model->hydrate ( $data )->persist (); if(is_string($result)){ $response ['error'] = true; $response ['message'] = $result; $this->log->error($response); }else { $exemption_id = $model->sequence; $response['metadata'] = ['exemption_id' => $exemption_id]; //add notify users if($on_behalf_of != ''){ $emails = preg_split('/[;, ]\s{0,1}/', $on_behalf_of); foreach ($emails as $email) { $email = trim($email); // check if email is valid in ldap $ldap = new LDAP(); $connect = $ldap->ldapConnect($CFG->ldap_host, $CFG->adsvcuser); $filter = "(mail=$email)"; $read = ldap_search($connect, $CFG->ldap_basedn, $filter, array( "mail" )) or header('Location: /error.html'); // or exit("error: unable to search ldap server."); $entries = ldap_get_entries($connect, $read); if ($entries['count'] === 0) { // @TODO check email is vodafone $response['error'] = true; $response['message'] = "Email not found - {$email}"; } else { $notifyUsers = new NotifyUsers(); $data = [ 'type' => 'exemption', 'type_id' => $exemption_id, 'email' => $email ]; $notifyUsers->hydrate($data)->persist(); } } } $response2 = $this->insertExemptionPolicies($model); // $this->log->fatal($response2); if (strtolower($response2['message']) == 'success') { $this->responseData = [ array_change_key_case($model->fieldVals, CASE_LOWER) ]; $this->dbdataCount = 1; // create local market exemption $vSecurityExemptionsNew = new VSecurityExemptionsNew(); $records = $vSecurityExemptionsNew->fetchAllArray([ 'id', 'src_approval', 'dst_approval' ], [ 'comms_matrix_id' => $model->fieldVals['COMMS_MATRIX_ID'] ]); $opco = $model->getLocalOpco(array_unique(array_column($records, 'src_approval')), array_unique(array_column($records, 'dst_approval'))); // $this->log->debug($opco); if (count($opco) == 1) { // add to local market exemptions $exemptionPolicies = new ExemptionPolicies(); $exemptionPolicies->addToLocalMarketExemptions($model, $opco); } } elseif ($response2['message'] == 'workerjob') { $response['message'] = 'Your request is being processed. You will be notified once it is complete.'; } elseif ($response2['error']) { $response['error'] = true; $response['message'] = $response2['message']; $this->log->error($response); } } } } } } if ($response['error'] && isset($model->fieldVals['ID']) && $model->fieldVals['ID']) { $this->log->warn("deleting exemption: " . $model->fieldVals['ID']); $this->log->warn($response); $db_rw = new DatabaseConnection('rw'); $sql = "select local_market from " . DBUSER . ".v_exemptions_group where id = :id"; $binds = [ ':id' => [ $model->fieldVals['ID'], 10, SQLT_INT ] ]; $local_market = $db_rw->executeToSingleCell($sql, $binds); // delete exemption $sql = "delete from " . DBUSER . ".exemptions where id = :id"; $binds = [ ':id' => [ $model->fieldVals['ID'], 10, SQLT_INT ] ]; $db_rw->execute($sql, $binds); if ($local_market != 'group' && ! empty(trim($local_market))) { $sql = "delete from " . DBUSER . ".exemptions_$local_market where id = :id"; $this->log->info($sql . " : " . $model->fieldVals['ID']); $db_rw->execute($sql, $binds); } } foreach ($response as $key => $value) { $this->{$key} = $value; unset($response[$key]); } return $this; } 

My background task which runs some of the above process

$memory_start = (memory_get_peak_usage(true)/1024/1024).' MiB'; //$this->log->debug('securityExemptionApprove'); $response = []; if(($job['args'] == 3) && (is_numeric($job['arg1']))) { $exemption_id = (int) $job['arg1']; $ids = (empty($job['arg2'])) ? [] : explode(',',$job['arg2']); $username = $job['arg3']; $exemptionPolicies = new ExemptionPolicies(); $ts_start = strtoupper ( date ( 'd-M-y h.i.s' ) ) . substr ( ( string ) microtime (), 1, 8 ); ini_set('memory_limit', '2048M'); $result = $exemptionPolicies->updateCommsMatrixAnalysisByPolicy($exemption_id, $ids, $username); $ts_end = strtoupper ( date ( 'd-M-y h.i.s' ) ) . substr ( ( string ) microtime (), 1, 8 ); $response ['metadata'] ['analysis_start'] = $ts_start; $response ['metadata'] ['analysis_end'] = $ts_end; $response ['metadata'] ['analysis_time'] = (strtotime ( $ts_end ) - strtotime ( $ts_start )) . ' seconds'; $response ['metadata'] ['memory'] ['start'] = $memory_start; $response ['metadata'] ['memory'] ['end'] = (memory_get_peak_usage(true)/1024/1024).' MiB'; if($result['error']) { $response['error'] = true; $response['message'] = $result['message']; $this->log->error('job #' . $job['id'] . ' failed (securityExemptionApprove)'); $update = [ 'FLAG' => 'T', 'ATTEMPTS' => $job['attempts'] + 1, 'TS_UPDATED' => 'systimestamp', 'OUTPUT' => \json_encode($response) ]; $condition = [ 'ID' => $job['id'], ]; $this->log->warn("setting to T in ".__FUNCTION__." for " . print_r($condition,true)); $workerJobs = new WorkerJobs(); $result = $workerJobs->updateTable($update, $condition); } else { $update = [ 'FLAG' => 'C', 'TS_UPDATED' => 'systimestamp', 'OUTPUT' => \json_encode($response) ]; $condition = [ 'ID' => $job['id'], ]; $workerJobs = new WorkerJobs(); $result = $workerJobs->updateTable($update, $condition); } } 

I can reveal more code of any required function if needed.

\$\endgroup\$
3
  • \$\begingroup\$ I'd recommend editing your post to make the title tell reviewers about the purpose of the code, rather than what concerns you have with it. Everyone wants cleaner code here! =) \$\endgroup\$ Commented Nov 6, 2019 at 21:28
  • \$\begingroup\$ Changing code in the question violates the question-and-answer nature of this site, because what's quoted in answers can no longer be found. You could post improved code as a new question, as an answer, or as a link to an external site - as described in I improved my code based on the reviews. What next?. I have rolled back the edit, so the answers make sense again. \$\endgroup\$ Commented Mar 17, 2021 at 12:45
  • \$\begingroup\$ @TobySpeight I need to anonymise the code or delete this question \$\endgroup\$ Commented Mar 18, 2021 at 18:27

1 Answer 1

8
\$\begingroup\$

Instead of putting comments above smaller blocks of code, convert those smaller blocks to private methods (we will change this later, for now keep them private methods of the same class to allow focus on other things).

But I would suggest you first familiarize yourself with IoC and DI as it will help you write much better code. And SOLID principles could also do you good.

Then anyway, to identify good blocks to replace by methods, you can:

1) look if some blocks look same or similar to some other blocks, like this:

if (isset($params['comms_matrix_id'])) { $params['comms_matrix_id_exemptions'] = $params['comms_matrix_id']; unset($params['comms_matrix_id']); } 

or here:

$sql = "SELECT id FROM ".DBUSER.".service_domains WHERE TRIM(name) = :name"; $binds = [':name' => [$service_domain,50,SQLT_CHR]]; $service_domain_id = $db->executeToSingleCell($sql,$binds); 

Another thing to look for is a piece of code starting where a new variable is introduced and ending where that variable is no longer used. Maybe like here:

$sysprivusers = new SysPrivUsers(); if ($sysprivusers->isSystemUser($user->getUsername())) { $isMemberOrUploaded = $user->isMemberOrUploaded((int) $comms_matrix_id); if ($isMemberOrUploaded['uploaded_by'] === null && $isMemberOrUploaded['member_of'] === null) { $response['error'] = true; $response['message'] = 'You are not authorised'; $this->log->error($isMemberOrUploaded); $this->log->error($user->getUsername()); foreach ($response as $key => $value) { $this->{$key} = $value; unset($response[$key]); } return $this; } } 

Bodies of for/foreach/while are also good candidates for separate functions.

When you have split the method into multiple ones. It is now time to separate them by domain. You use a lot of in-place instantiation of service classes. Put them into the class's properties and instantiate them in constructor. When you do this it will help you identify methods that are related or have the same dependency. Although this is not necesary to be able to separate them by domain, it might help a little and it is a good idea nevertheless.

When you are clear about which functions are related to which other functions, they will form clusters. Each of those clusters can be moved to a separate class and then have the main class depend on those instead. If some of those clusters have too much dependencies (lets say 4 or more) it may be sign that their methods could be split better and you basicaly repeat the process, until pretty much every class has one and only responsibility (or one could say one purpose, one reason to change). Good sign of that is that number of private/protected methods is going down, while number of classes and public methods is going up (but number of methods per class is also going down). And btw if you start having classes that are mutualy dependent you are probably doing something wrong.

During the process, you may find some optimizations for some of the smaller blocks. You may figure that something can be done in a different way. Or get confident enough to put some methods to their own class right away.

Try to avoid reusing of variables. Use good names for variables and classes and methods, so in the end you can read the code just as easy as you read a book.

And that would be about it. There's definitely more you could do but I'm not sure you are ready for that just now... Take it one step at a time...

EDIT: As pointed out by Iiridayn, it is definitely not necesary to reach the final state as I described it. What I described is kind of utopia, a state of perfection... Something you should try to reach but never completely will. Anything between the current state and the state of perfection is going to be better then the current state. At some point you might want to stop and consider the changes made so far enough for the time being. I would object though that the final state would yield a ton of classes, I'd say more like a dozen or two at most :)

Another thought related to this is that when you are looking for reusable blocks to move to separate functions, you might also want to consider other parts of your code that you didnt show us in your question. Like other controllers. Chances are high that there are some pieces that are the same across multiple controllers (or generaly across multiple objects of the same type/pattern). These pieces are good candidates for methods that should be separated from your main class, whereas methods that have no similarities with any other pieces of your application might be those that you may want to keep in the main class for now...

And yet one more note: The process I described cannot be seemed as an algorithm you can follow without thinking and reach the desired goal. It will need a lot of thinking about what you are doing and deciding when to apply some of the rules I mentioned and when not. It might need a lot of creativity and invention. You will find your own rules to apply. The only rule of thumb is, that no rule can be followed in all cases.

\$\endgroup\$
2
  • \$\begingroup\$ I agree that the functional decomposition is an excellent idea. I'm leery of creating a ton of classes though. Functional decomposition + grouping might be enough for now. Should there be enough related functionality, dumping it into a module (a class is one kind of module) at that point (enough to justify a file, about 400 lines of closely related code or so) might be worth it. Too much decomposition can become premature optimization - not of runtime, but of structure. \$\endgroup\$ Commented Nov 7, 2019 at 5:24
  • \$\begingroup\$ @Iiridayn You are absolutely correct. I have incorporated your point into my answer. Thank you for mentioning it. \$\endgroup\$ Commented Nov 7, 2019 at 6:38

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.