2

I created a PHP class that looks up and returns an XML list of nearby schools when you input longitude and latitude. I output them into three HTML tables, one for Elementary, Middle, and High School. The XML source file doesn't include that information, only what range of grades the school provides, so I sort them using regex, like this:

foreach($data as $school) {
       if (preg_match("/1[0-2]$/", $school->gradeRange)) {?>
       <tr><td><?=$school->name?></td>
       <td><address><?=$school->address?>, <?=$school->city?></address></td>

And so on. This works just fine, but that code is on the content page and I need to move the sorting mechanism into the class, and this is befuddling me. I'm new to this, so it may be something obvious, but I've been searching and reading all day and can't find the solution.

In the class I created this function:

function sortSchools() {

        foreach($this->xml as $school) {
            if (preg_match("/1[0-2]$/", $school->gradeRange)) {
                $this->high = "true";
            }

        }
    }

But it doesn't actually do any work to data itself. Is there a way to have this function split the schools where the gradeRange nodes match the expression into their own array, so I'd end up with a separate array for each school level? I've searched everything I can think to search, both on here and Google, and I'm coming up empty handed.

If this is a dumb question, I apologize, I've only been doing this for about a week.

halfer
  • 19,824
  • 17
  • 99
  • 186

2 Answers2

0

I did something quick here, that's actually not very beautiful, but it may help.

<?php

class School {
    public $gradeRange;
    public $name;
    public $address;
    public $city;

    public $high;

    public function __construct($gradeRange, $name, $address, $city) {
        $this->gradeRange = $gradeRange;
        $this->name = $name;
        $this->adress = $address;
        $this->city = $city;
    }
}

$data = array(
    new School(112, "springfield hs", "99 strt", "Springfield"),
    new School(111, "stuff hs", "blvd stuff", "stufftown"),
    new School(134, "univerisity (not an highschool)", "here", "charlestown"),
    new School(110, "paul", "23 rd", "poll"),
);

foreach ($data as $school) {
    if (preg_match("/1[0-2]$/", $school->gradeRange)) { ?>
        <tr>
            <td><?= $school->name ?></td>
            <td>
                <address><?= $school->address ?>, <?= $school->city ?></address>
            </td>
        </tr>
    <?php }
}

class SchoolSorter
{
    public $schools;

    /**
     * @param array|School $schools
     */
    public function __construct(array $schools)
    {
        //$xmlReadre = new XMLReader();
        //$xmlReadre->readString($xml);
        $this->schools = $schools;
    }

    public function sortSchools()
    {
        foreach ($this->schools as $school) {
            if (preg_match("/1[0-2]$/", $school->gradeRange)) {
                $school->high = "true";
            }
        }
    }
}

$that_scholl_sorter = new SchoolSorter(
    $data
);

echo "before: \n";
var_dump($that_scholl_sorter->schools);

$that_scholl_sorter->sortSchools();

echo "after: \n";
var_dump($that_scholl_sorter->schools);

which outputs like this

        <tr>
            <td>springfield hs</td>
            <td>
                <address>, Springfield</address>
            </td>
        </tr>
            <tr>
            <td>stuff hs</td>
            <td>
                <address>, stufftown</address>
            </td>
        </tr>
            <tr>
            <td>paul</td>
            <td>
                <address>, poll</address>
            </td>
        </tr>
    before: 
array(4) {
  [0]=>
  object(School)#1 (6) {
    ["gradeRange"]=>
    int(112)
    ["name"]=>
    string(14) "springfield hs"
    ["address"]=>
    NULL
    ["city"]=>
    string(11) "Springfield"
    ["high"]=>
    NULL
    ["adress"]=>
    string(7) "99 strt"
  }
  [1]=>
  object(School)#2 (6) {
    ["gradeRange"]=>
    int(111)
    ["name"]=>
    string(8) "stuff hs"
    ["address"]=>
    NULL
    ["city"]=>
    string(9) "stufftown"
    ["high"]=>
    NULL
    ["adress"]=>
    string(10) "blvd stuff"
  }
  [2]=>
  object(School)#3 (6) {
    ["gradeRange"]=>
    int(134)
    ["name"]=>
    string(31) "univerisity (not an highschool)"
    ["address"]=>
    NULL
    ["city"]=>
    string(11) "charlestown"
    ["high"]=>
    NULL
    ["adress"]=>
    string(4) "here"
  }
  [3]=>
  object(School)#4 (6) {
    ["gradeRange"]=>
    int(110)
    ["name"]=>
    string(4) "paul"
    ["address"]=>
    NULL
    ["city"]=>
    string(4) "poll"
    ["high"]=>
    NULL
    ["adress"]=>
    string(5) "23 rd"
  }
}
after: 
array(4) {
  [0]=>
  object(School)#1 (6) {
    ["gradeRange"]=>
    int(112)
    ["name"]=>
    string(14) "springfield hs"
    ["address"]=>
    NULL
    ["city"]=>
    string(11) "Springfield"
    ["high"]=>
    string(4) "true"
    ["adress"]=>
    string(7) "99 strt"
  }
  [1]=>
  object(School)#2 (6) {
    ["gradeRange"]=>
    int(111)
    ["name"]=>
    string(8) "stuff hs"
    ["address"]=>
    NULL
    ["city"]=>
    string(9) "stufftown"
    ["high"]=>
    string(4) "true"
    ["adress"]=>
    string(10) "blvd stuff"
  }
  [2]=>
  object(School)#3 (6) {
    ["gradeRange"]=>
    int(134)
    ["name"]=>
    string(31) "univerisity (not an highschool)"
    ["address"]=>
    NULL
    ["city"]=>
    string(11) "charlestown"
    ["high"]=>
    NULL
    ["adress"]=>
    string(4) "here"
  }
  [3]=>
  object(School)#4 (6) {
    ["gradeRange"]=>
    int(110)
    ["name"]=>
    string(4) "paul"
    ["address"]=>
    NULL
    ["city"]=>
    string(4) "poll"
    ["high"]=>
    string(4) "true"
    ["adress"]=>
    string(5) "23 rd"
  }
}

In your example, you were using $this->high = "true";, but you probably meant $school->high = "true";.

It's not splitting data based on the match, but you could loop on an array of regular expressions and pass the regex as a parameter to sortSchools($regex), do the preg_match on that parameter and have the function return an array of matching schools.

No dumb question here ;).

Instead of preg_match, you could also use preg_grep to get the indexes of the matching schools in an array

preg_grep("/1[0-2]$/", explode("\n", $input_lines));. You may find this php live regex tester useful.

Note that you could also pass schools by reference, see PHP Documentation, but that is not recommended, see this other question.

Community
  • 1
  • 1
GabLeRoux
  • 16,715
  • 16
  • 63
  • 81
0

Looking at the first code-example in your question:

foreach ($data as $school) {
   if (preg_match("/1[0-2]$/", $school->gradeRange)) {?>
   <tr><td><?=$school->name?></td>
   <td><address><?=$school->address?>, <?=$school->city?></address></td>

You have a typical filter-condition here with your if clause.

You now want to move it outside of the template and more inside $data.

As $data is a simplexml element (I guess) and you haven't shared any details of the School class you've been written, I actually can't tell you how you finally move it into that class, however you can easily create yourself a FilterIterator that is able to work on a more specific SimpleXMLElement: SimpleXMLIterator (or you can wrap the existing element once.

Such a filter is easy to implement:

class HighSchools extends FilterIterator
{
    public function accept()
    {
        $school = $this->getInnerIterator()->current();

        return preg_match("/1[0-2]$/", $school->gradeRange);
    }
}

This on it's own (given you instantiate a SimpleXMLIterator instead of a SimpleXMLIterator which should work w/o problems) allows you to move the filter-condition out-of the foreach:

$highSchools = new HighSchools($data);

foreach ($highSchools as $school) {
    echo $school->name, "\n";
}

As it's likely you won't have a single filter, you can create a family of filter classes so that's easier to write more than one filter and reducing the duplicate code:

abstract class SchoolFilter extends FilterIterator
{
    final public function accept()
    {
        $school = $this->getInnerIterator()->current();

        return $this->acceptSchool($school);
    }
}

class HighSchools extends SchoolFilter
{
    public function acceptSchool($school)
    {
        return preg_match("/1[0-2]$/", $school->gradeRange);
    }
}

You can then move it on inside the school class. It's even possible to extend from SimpleXMLIterator and make a special one that is able to offer accessor methods to filtered collections of itself.

Another alternative would be that you off the classification / typification of the schools inside the XML already so that you can as well easily query the document with xpath.

I hope this offers some paths to look into towards a more modular design so that it's easier to you to find places where to put the filtering conditions as well as make them interchangeable.

Because you don't need three arrays here. You're just looking for displaying three times the same data but just in a different fashion. The data is there just once - not three times. Keep it that simple then you can do a thousand different ways to present that data with little code modifications.

hakre
  • 193,403
  • 52
  • 435
  • 836