1

I am having a few issues building up quite a complex array. I make 4 different API calls and each of them return simpleXML data which I will show below.

With one API call I essentially get returned a bunch of Leads. For each Lead, I then need collect whatever data I can for that Lead. This is an example response of a Lead.

SimpleXMLElement {#289 ▼
  +"Leads": SimpleXMLElement {#297 ▼
    +"Lead": array:12 [▼
      0 => SimpleXMLElement {#300 ▼
        +"ID": "1266283"
        +"State": "Current"
        +"Name": "Test project"
        +"Description": "Test project"
        +"EstimatedValue": "2.00"
        +"Date": "2016-05-26T00:00:00"
        +"Client": SimpleXMLElement {#316 ▼
          +"ID": "8549201"
          +"Name": "Test Client"
        }
      }
    ]
  }
}

I then have a bunch of client XML data. I need to link the Lead to the Client and collect some client data. This is an example data section for a Client.

SimpleXMLElement {#290 ▼
  +"Clients": SimpleXMLElement {#298 ▼
    +"Client": array:41 [▼
      34 => SimpleXMLElement {#335 ▼
        +"ID": "8549201"
        +"Name": "Test Client"
        +"IsProspect": "No"
        +"BusinessStructure": "IT"
      }
    ]
  }
}

So at this point, I have Leads and I have matched the correct Client to a Lead and obtained data about that client. The nest two API calls refers to Quotes. One API call returns all current Quotes. Another API call returns draft quotes.

This is an example for current Quotes

SimpleXMLElement {#291 ▼
  +"Quotes": SimpleXMLElement {#299 ▼
    +"Quote": array:24 [▼
      0 => SimpleXMLElement {#302 ▼
        +"ID": "Q12415"
        +"Type": "Quote"
        +"State": "Issued"
        +"Name": "Test Quote"
        +"LeadID": "1266283"
        +"Date": "2016-05-20T00:00:00"
        +"Amount": "6100.00"
        +"AmountTax": "1220.00"
        +"AmountIncludingTax": "7320.00"
        +"Client": SimpleXMLElement {#331 ▼
          +"ID": "8549201"
          +"Name": "Test Client"
        }
      }
    ]
  }
}

This is an example for draft Quotes

SimpleXMLElement {#292 ▼
  +"Quotes": SimpleXMLElement {#300 ▼
    +"Quote": SimpleXMLElement {#303 ▼
      +"ID": "Q12456"
      +"Type": "Quote"
      +"State": "Draft"
      +"Name": "Test project"
      +"LeadID": "1266283"
      +"Date": "2016-05-26T00:00:00"
      +"Amount": "2000.00"
      +"AmountTax": "400.00"
      +"AmountIncludingTax": "2400.00"
      +"Client": SimpleXMLElement {#305 ▼
        +"ID": "8549201"
        +"Name": "Test Client"
      }
    }
  }
}

Anyways, this is what I currently have

public function getForecastReportForLeads() {
    $getCurrentLeads = Helper::getCurrentLeads();
    $currentLeadsXML = new \SimpleXMLElement($getCurrentLeads);

    $getCurrentClients = Helper::getClientList();
    $currentClientsXML = new \SimpleXMLElement($getCurrentClients);

    $getCurrentQuotes = Helper::getCurrentQuotes();
    $currentQuotesXML = new \SimpleXMLElement($getCurrentQuotes);

    $getDraftQuotes = Helper::getDraftQuotes();
    $draftQuotesXML = new \SimpleXMLElement($getDraftQuotes);

    $forecastArray = array();
    $iterator = 0;

    foreach($currentLeadsXML->Leads->Lead as $lead) {
        $seconditerator = 0;
        $thirditerator = 0;
        $fourthiterator = 0;

        $dateIdentified = date("d/m/Y", strtotime($lead->Date));
        $forecastArray[$iterator]["leadData"] = array(
            'LeadID' => (string)$lead->ID,
            'DateIdentified' => $dateIdentified,
            'Client' => (string)$lead->Client->Name,
            'LeadName' => (string)$lead->Name,
            'Owner' => (string)$lead->Owner->Name,
            'Category' => (string)$lead->Category
        );

        foreach ($currentClientsXML->Clients->Client as $client) {
            if((string)$lead->Client->Name == $client->Name) {
                $forecastArray[$iterator]["clientData"] = array(
                    'BusinessStructure' => (string)$client->BusinessStructure,
                    'IsProspect' => (string)$client->IsProspect
                );
                $seconditerator++;
            }
        }

        foreach ($currentQuotesXML->Quotes->Quote as $quote) {
            if ((string)$lead->ID == (string)$quote->LeadID) {
                $forecastArray[$iterator]["quoteDataIssued"] = array(
                    'QuoteID' => (string)$quote->ID,
                    'ProjectName' => (string)$quote->Name,
                    'Amount' => (string)$quote->Amount,
                    'AmountTax' => (string)$quote->AmountTax,
                    'AmountIncludingTax' => (string)$quote->AmountIncludingTax
                );
                $thirditerator++;
            }
        }

        foreach ($draftQuotesXML->Quotes->Quote as $draftQuote) {
            if ((string)$lead->ID == (string)$draftQuote->LeadID) {
                $forecastArray[$iterator]["quoteDataDraft"] = array(
                    'QuoteID' => (string)$draftQuote->ID,
                    'ProjectName' => (string)$draftQuote->Name,
                    'Amount' => (string)$draftQuote->Amount,
                    'AmountTax' => (string)$draftQuote->AmountTax,
                    'AmountIncludingTax' => (string)$draftQuote->AmountIncludingTax
                );
                $fourthiterator++;
            }
        }
        $iterator++;
    }

    return $forecastArray;
}

One thing to note is that sometimes a Quote does not have a LeadID. If this is the case, the Quote can be ignored which is something I am not currently handling.

Another thing to note is that I only want to get this data if the client the lead is related too has a IsProspect value of "No". This is something else I am struggling to get in place.

Any advice on getting this properly in place with my requirements, or how I can improve on my current code greatly appreciated.

Many thanks

katie hudson
  • 2,765
  • 13
  • 50
  • 93
  • So your goal is to clean up presented code and make it running correctly, right? – michaJlS May 28 '16 at 20:39
  • Hi, that is correct. I fixed one thing so I have updated the OP. I think the code works in the most part. One problem I am having is displaying it within a table. If a Lead has more than one quote this is where it messes up. Thanks – katie hudson May 28 '16 at 20:55
  • Why did you put there all those $iterator counters? – michaJlS May 28 '16 at 20:58

1 Answers1

1

Small clean up

Just get rid of counters, and code will be much more readable:

function getForecastReportForLeads()
{

    /* ... */

    $forecastArray = array();

    foreach($currentLeadsXML->Leads->Lead as $lead) {

        $reportItem = array;

        $dateIdentified = date("d/m/Y", strtotime($lead->Date));
        $reportItem["leadData"] = array(/* ... */);

        foreach ($currentClientsXML->Clients->Client as $client) {
            if((string)$lead->Client->Name == $client->Name) {
                $reportItem["clientData"] = array(/* ... */);
                if ('No' != (string)$client->IsProspect) {
                     continue;
                }
            }
        }

        foreach ($currentQuotesXML->Quotes->Quote as $quote) {
            if ((string)$lead->ID == (string)$quote->LeadID) {
                $forecastArray["quoteDataIssued"][] = array(/* ... */);
            }
        }

        foreach ($draftQuotesXML->Quotes->Quote as $draftQuote) {
            if ((string)$lead->ID == (string)$draftQuote->LeadID) {
                $reportItem["quoteDataDraft"][] = array(/* ... */);
            }
        }

        $forecastArray[] = $reportItem;
    }

    return $forecastArray;
}

And to omit clients with prospect!=No simply go for :

if ('No' != (string)$client->IsProspect) {
    continue;
}

as shown above. That will work as long as there is a client for every lead.

Larger refactoring

And the second option. To improve that code you can try to move logic responsible for traversing XMLs and converting XML to arrays to separate classes. One class per data collection. So here is possible structure of such classes:

class LeadsCollection
{

    function __construct(\SimpleXMLElement $leads){}

    /**
     * Returns list of leads in form of assoc arrays.
     *
     * @return array
     */
    function asArray(){}

}


class ClientsCollection
{

    function __construct(\SimpleXMLElement $clients){}

    /**
     * @return array
     */
    function getClientByName($name){}

    /**
     * retub bool
     */
    function isProspect($name){}

}

class QuotesCollection
{

    function __construct(\SimpleXMLElement $quotes){}

    /**
     * @retun array|null - list of quotes related with given lead
     */
    function getQuotesByLeadId($leadId){}

}

class DraftQuotesCollection
{

    function __construct(\SimpleXMLElement $draftQuotes){}

    /**
     * @retun array|null - list of draft quotes related with given lead
     */
    function getDraftQuotesByLeadId($leadId){}
}

And then your array creation code will be much much cleaner and more concise:

function getForecastReportForLeads() {
    $leads = new LeadsCollection(new \SimpleXMLElement(Helper::getCurrentLeads()));
    $clients = new ClientsCollection(new \SimpleXMLElement(Helper::getClientList());
    $quotes = new QuotesCollection(new \SimpleXMLElement(Helper::getCurrentQuotes()));
    $draftQuotes = new DraftQuotesCollection(Helper::getDraftQuotes(new \SimpleXMLElement(Helper::getDraftQuotes())));

    $report = array();
    foreach ($leads->asArray() as $lead) {

        if ($clients->isProspect($lead['Client'])) {
            continue;
        }

        $clientData = $clients->getClientByName($lead['Client']);
        $quotesData = $quotes->getQuotesByLeadId($lead['LeadId']);
        $draftQuotesData = $draftQuotes->getDraftQuotesByLeadId($lead['LeadId']);

        $reportItem = array(
            'leadData' => $lead,
            'clientData' => $clientData,
        );

        empty($quotesData) || $reportItem['quoteDataIssued'] = $quotesData;
        empty($draftQuotesData) || $reportItem['quoteDataDraft'] = $draftQuotesData;

        $report[] = $reportItem;
    }

    return $report;
}

Third way

If for any reason, you don't want to put 4 new classes into your project, then instead you can try to breakdown your monolith method into several private methods. You can even follow "design" suggested above, but instead of having 4 classes, you can put all their methods into single one. Not the cleanest solution, but still better than having one big procedural in style method. And can make sense if you know that you will not need to reuse that code in any other place of your system.

Remarks

Depending on the size of your data, your approach with iterating through whole data set again and again may be not most effective:

        foreach ($currentClientsXML->Clients->Client as $client) {
            if((string)$lead->Client->Name == $client->Name) {
                 /* ... */

It may pay off if you rearrange/group leads and quotes by variables, by which you want to access it, so by leadId. and client name.

michaJlS
  • 2,465
  • 1
  • 16
  • 22
  • Thanks, one of the big problems I was having is displaying the data in a table. If a Lead had more than one quote, I was having difficulty showing this extra quote on a new tr. Would this be possible? – katie hudson May 29 '16 at 09:00
  • Can you attach screenshot or something to show how are you displaying it now? – michaJlS May 29 '16 at 09:08
  • I dont really have anything yet. I was going to do each array element as a table header. One lead per row but if a lead has more than one quote display these on a new row. – katie hudson May 29 '16 at 11:20
  • 1
    @kate_hudson you may find colspan and rowspan helpful in your task if you don't know them http://www.htmlcodetutorial.com/tables/index_famsupp_30.html + http://stackoverflow.com/questions/9830506/how-do-you-use-colspan-and-rowspan-in-html-tables – michaJlS May 29 '16 at 21:17
  • I just realised that a lead may have more than one quote - the example you provided does not handle multiple quotes does it? This was why I initially had the iterator. – katie hudson May 30 '16 at 18:44
  • 1
    @kate_hudson it does. I updated my answer once I've read that you may need that;) It doesn't support many leads per client, though it would be not difficult to add. You know, that you can just add an element to array in PHP without knowing what will its key, and that PHP will change automatically its size etc, right? http://php.net/manual/en/language.types.array.php#language.types.array.syntax.modifying it is pretty straightforward and intuitive and forgiving. – michaJlS May 30 '16 at 19:04