~fabiocbalbuquerque/sahana-agasti/web-services

« back to all changes in this revision

Viewing changes to apps/frontend/lib/util/agAddressHelper.class.php

  • Committer: Chad Heuschober
  • Date: 2011-03-20 14:02:52 UTC
  • mfrom: (1.1.596 trunk)
  • mto: This revision was merged to the branch mainline in revision 4.
  • Revision ID: chad.heuschober@mail.cuny.edu-20110320140252-kgaed7k4e9icemtx
Applying updates from the week of March 14, from the CUNY SPS core team.

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
            ADDR_GET_GEO = 'getAddressCoordinates',
21
21
            ADDR_GET_TYPE = 'getAddressComponentsByName',
22
22
            ADDR_GET_LINE = 'getAddressComponentsByLine',
23
 
            ADDR_GET_STRING = 'getAddressAsString';
24
 
 
25
 
  public    $lineDelimiter = "\n",
26
 
            $enforceComplete = TRUE,
 
23
            ADDR_GET_STRING = 'getAddressAsString',
 
24
            ADDR_GET_NATIVE_LINE = 'getNativeAddressComponentsByLine',
 
25
            ADDR_GET_NATIVE_STRING = 'getNativeAddressAsString' ;
 
26
 
 
27
 
 
28
  public    $lineDelimiter = "<br />",
 
29
            $enforceComplete = FALSE,
27
30
            $enforceLineNumber = FALSE,
28
31
            $checkValuesForCompleteness = FALSE ;
29
32
 
37
40
            $_addressGeoTypeId;
38
41
 
39
42
  /**
 
43
   * Overloaded magic call method to provide access to the getNativeAddress* variants.
 
44
   *
 
45
   * @param string $method The method being called.
 
46
   * @param array $arguments The arguments being passed.
 
47
   * @return function call
 
48
   */
 
49
  public function __call($method, $arguments)
 
50
  {
 
51
    $nativePreg = '/^getNativeAddress/i' ;
 
52
    
 
53
    // check to see if our method exists in our helpers
 
54
    if (preg_match($nativePreg, $method))
 
55
    {
 
56
      try
 
57
      {
 
58
        // parse out the function that's *really* being called
 
59
        $returnMethod = array($this, preg_replace($nativePreg, 'getAddress', $method)) ;
 
60
        $nativeMethod = array($this, '_getNativeAddress') ;
 
61
 
 
62
        // execute and return
 
63
        return call_user_func_array($nativeMethod, array($returnMethod, $arguments)) ;
 
64
      }
 
65
      catch (Exception $e)
 
66
      {
 
67
        // if there's an error, write to log and return
 
68
        $notice = sprintf('Execution of the %s method, found in %s failed. Attempted to use the
 
69
          parent class.', $method, $helperClass) ;
 
70
        sfContext::getInstance()->getLogger()->notice($notice) ;
 
71
      }
 
72
    }
 
73
    
 
74
    return parent::__call($method, $arguments) ;
 
75
  }
 
76
 
 
77
  /**
40
78
   * This is the class's constructor whic pre-loads the formatting elements according to the default
41
79
   * return standard.
42
80
   *
144
182
  }
145
183
 
146
184
  /**
 
185
   *
 
186
   * @param <type> $method
 
187
   * @param <type> $arguments
 
188
   */
 
189
  protected function _getNativeAddress($returnMethod, $arguments)
 
190
  {
 
191
    // always nice to have results, don'cha think?
 
192
    $results = array() ;
 
193
 
 
194
    // pick this up so we can reset it when done!
 
195
    $origStandardId = $this->_returnStandardId ;
 
196
 
 
197
    // pick up our all of our address ids
 
198
    $addressIds = array_shift($arguments) ;
 
199
    $addressIds = $this->getRecordIds($addressIds) ;
 
200
 
 
201
    // seems a little insane but we do this so our foreach can always replace [0]
 
202
    array_unshift($arguments, array()) ;
 
203
 
 
204
    // construct our standards query
 
205
    $q = agDoctrineQuery::create()
 
206
      ->select('a.address_standard_id')
 
207
          ->addSelect('a.id')
 
208
        ->from('agAddress a')
 
209
        ->whereIn('a.id', $addressIds) ;
 
210
 
 
211
    // execute the standards query and return a grouped array
 
212
    $addrStandards = $q->execute(array(), agDoctrineQuery::HYDRATE_ASSOC_ONE_DIM) ;
 
213
 
 
214
    // loop through the standards and set the formatting components appropriately
 
215
    foreach ($addrStandards as $standardId => $addressIds)
 
216
    {
 
217
      // start by setting the new standard to be processed (use the if to avoid spurious sets)
 
218
      if ($standardId != $this->_returnStandardId) { $this->setReturnStandard($standardId) ; }
 
219
 
 
220
      // set the addressIds argument (always first)
 
221
      $arguments[0] = $addressIds ;
 
222
 
 
223
      // append the call for just those standards to our results set
 
224
      $subResults = call_user_func_array($returnMethod, $arguments) ;
 
225
      $results = $results + $subResults ;
 
226
 
 
227
      // release the resources for this standard
 
228
      unset($addrStandards[$standardId]) ;
 
229
    }
 
230
 
 
231
    // reset our original standard (use the if to avoid spurious sets)
 
232
    if ($origStandardId != $this->_returnStandardId) { $this->setReturnStandard($origStandardId) ; }
 
233
 
 
234
    return $results ;
 
235
  }
 
236
 
 
237
  /**
147
238
   * Method used to construct the base query object used by other objects in this class.
148
239
   *
149
240
   * @param array $addressIds A single-dimension array of address  id's.
180
271
    // return our base query object
181
272
    $q = $this->_getAddressComponents($addressIds) ;
182
273
 
183
 
    $results = $q->execute(array(), 'assoc_two_dim');
184
 
    return $results ;
 
274
    return $q->execute(array(), agDoctrineQuery::HYDRATE_ASSOC_TWO_DIM);
185
275
  }
186
276
 
187
277
  /**
260
350
      $results[$row[0]][$row[3]] = $row[2] ;
261
351
    }
262
352
 
 
353
    // release the rows resource
 
354
    unset($rows) ;
 
355
 
263
356
    // grab our geo coordinates and merge them to the array
264
357
    if ($getGeoCoordinates) {
265
358
      $geoCoordinates = $this->getAddressCoordinates($addressIds) ;
422
515
                                      $enforceComplete = NULL,
423
516
                                      $enforceLineNumber = NULL)
424
517
  {
425
 
    // always a good idea to explicitly declare this
426
 
    $results = array() ;
427
 
 
428
518
    // grab our default if not explicitly passed a line number parameter
429
519
    if (is_null($enforceLineNumber)) { $enforceLineNumber = $this->enforceLineNumber ; }
430
520
 
431
521
    // now we grab all of our addresses and return them in an array sorted per-line
432
522
    $addresses = $this->getAddressComponentsByLine($addressIds, $enforceComplete) ;
433
523
 
 
524
    // release the addressId array
 
525
    unset($addressIds) ;
 
526
 
434
527
    // start by iterating over the individual addresses
435
528
    foreach ($addresses as $addressId => $addrLines)
436
529
    {
463
556
      }
464
557
 
465
558
      // add it to our results array
466
 
      $results[$addressId] = $strAddr ;
467
 
    }
468
 
 
469
 
    return $results ;
 
559
      $addresses[$addressId] = $strAddr ;
 
560
    }
 
561
 
 
562
    return $addresses ;
 
563
  }
 
564
 
 
565
  /**
 
566
   * Method to return a flattened address query object, based on the standard being applied.
 
567
   * @return Doctrine Query A doctrine query object.
 
568
   * @deprecated This function was never used/implemented.
 
569
   */
 
570
  protected function _getFlatAddresses()
 
571
  {
 
572
    // start a basic query object with just the address
 
573
    $q = agDoctrineQuery::create()
 
574
      ->select('a.id')
 
575
        ->from('agAddress a') ;
 
576
 
 
577
    // had to be a little creative here using subqueries in the select because doctrine's rather
 
578
    // fussy, but the workaround suits the need
 
579
    // basically, we just loop through 'all' of the elements in our current standard and build a
 
580
    // pivoted (aka crosstab) variant of the address with a column for each of those elements
 
581
    foreach ($this->_addressAllowedElements as $elemId => $elem)
 
582
    {
 
583
      // don't ask me why, but doctrine freaks if you try to move the ON clause to a new line
 
584
      $selectStatement = '(SELECT av%1$02d.id
 
585
        FROM agAddressMjAgAddressValue amav%1$02d
 
586
          INNER JOIN amav%1$02d.agAddressValue av%1$02d ON amav%1$02d.address_value_id = av%1$02d.id
 
587
          WHERE amav%1$02d.address_id = a.id
 
588
            AND av%1$02d.address_element_id = %1$02d) AS %s' ;
 
589
      $selectStatement = sprintf($selectStatement, $elemId, $elemId) ;
 
590
      $q->addSelect($selectStatement);
 
591
    }
 
592
 
 
593
    return $q ;
 
594
  }
 
595
 
 
596
  /**
 
597
   * Method to update the address hash values of existing addresses.
 
598
   *
 
599
   * @param array $addressIds A single dimension array of addressIds.
 
600
   * @param Doctrine_Connection $conn An optional doctrine connection object.
 
601
   * @deprecated This should not normally be necessary as address hashes should be generated
 
602
   * at address creation.
 
603
   */
 
604
  public function updateAddressHashes($addressIds = NULL, $conn = NULL)
 
605
  {
 
606
    // reflect our addressIds
 
607
    $addressIds = $this->getRecordIds($addressIds) ;
 
608
 
 
609
    // pick up a default conneciton if none is passed
 
610
    if (is_null($conn)) { $conn = Doctrine_Manager::connection() ; }
 
611
 
 
612
    // use our componentsId getter to get all of the relevant components for these ids
 
613
    $addressComponents = $this->getAddressComponentsById($addressIds) ;
 
614
 
 
615
    // here we set up our collection of address records, selecting only those with the addressIds
 
616
    // we're affecting. Note: INDEXBY is very important if we intend to access this collection via
 
617
    // array access as we do later.
 
618
    $q = agDoctrineQuery::create($conn)
 
619
      ->select('a.*')
 
620
        ->from('agAddress a INDEXBY a.id')
 
621
        ->whereIn('a.id', $addressIds) ;
 
622
    $addressCollection = $q->execute() ;
 
623
 
 
624
    foreach ($addressComponents as $addressId => $components)
 
625
    {
 
626
      // calculate the component hash
 
627
      $addrHash = $this->hashAddress($components) ;
 
628
 
 
629
      // update the address hash value of this addressId by array access
 
630
      $addressCollection[$addressId]['address_hash'] = $addrHash ;
 
631
    }
 
632
 
 
633
    // start our transaction
 
634
    $conn->beginTransaction() ;
 
635
    try
 
636
    {
 
637
     $addressCollection->save() ;
 
638
     $conn->commit() ;
 
639
    }
 
640
    catch(Exception $e)
 
641
    {
 
642
      // if that didn't pan out so well, execute a rollback and log our woes
 
643
      $conn->rollback() ;
 
644
      
 
645
      $message = ('One of the addresses in your addressId collection could not be updated.
 
646
        No changes were applied.') ;
 
647
      sfContext::getInstance()->getLogger()->err($message) ;
 
648
      throw new sfException($message, $e) ;
 
649
    }
 
650
  }
 
651
 
 
652
  /**
 
653
   * Method to take an address component array and return a json encoded, md5sum'ed address hash.
 
654
   * @param array $addressComponentArray An associative array of address components keyed by
 
655
   * elementId with the string value.
 
656
   * @return string(128) A 128-bit md5sum string.
 
657
   */
 
658
  protected function hashAddress($addressComponentArray)
 
659
  {
 
660
    // first off, we don't trust the sorting of the address components so we do our own
 
661
    ksort($addressComponentArray) ;
 
662
 
 
663
    // we json encode the return to
 
664
    return md5(json_encode($addressComponentArray)) ;
 
665
  }
 
666
 
 
667
  /**
 
668
   * A quick helper method to take in an array address hashes and return an array of address ids.
 
669
   * @param array $addressHashes A monodimensional array of md5sum, json_encoded address hashes.
 
670
   * @return array An associative array, keyed by address hash, with a value of address_id.
 
671
   */
 
672
  public function getAddressIdsByHash($addressHashes)
 
673
  {
 
674
    $q = agDoctrineQuery::create()
 
675
      ->select('a.address_hash')
 
676
          ->addSelect('a.id')
 
677
        ->from('agAddress a')
 
678
        ->whereIn('a.address_hash',$addressHashes) ;
 
679
 
 
680
    return $q->execute(array(), agDoctrineQuery::HYDRATE_KEY_VALUE_PAIR) ;
 
681
  }
 
682
 
 
683
  /**
 
684
   * Method to take in address components and return address ids, inserting new addresses OR
 
685
   * address components as necessary.
 
686
   *
 
687
   * NOTE: This method does not fail fast. Addresses for which address id's could not be returned,
 
688
   * either by failed search or failed insert, are returned by index as part of the results set.
 
689
   *
 
690
   * @param array $addresses This multi-dimensional array of address data is keyed by an arbitrary
 
691
   * index. The values of each index are: an array of address components, keyed by element id, and the
 
692
   * default address standard of this address.
 
693
   * <code>
 
694
   * array(
 
695
   *    [$index] => array(
 
696
   *      [0] => array( [$elementId] => $valueString, ...),
 
697
   *      [1] => $addressStanardId
 
698
   *    ),
 
699
   *    ...
 
700
   * )
 
701
   * </code>
 
702
   * @param Doctrine_Connection $conn A doctrine connection object.
 
703
   * @return array A two dimensional array. The first array element ([0]), returns an array of
 
704
   * address indexes and the newly inserted addressIds. The second array element [1], returns all
 
705
   * address indexes that could not be inserted.
 
706
   * <code>
 
707
   * array(
 
708
   *  [0] => array( [$addressIndex] => [$addressId], ... )
 
709
   *  [1] => array( $addressIndex, ... )
 
710
   * )
 
711
   * </code>
 
712
   */
 
713
  public function setAddresses($addresses, Doctrine_Connection $conn = NULL)
 
714
  {
 
715
    // declare our results array
 
716
    $results = array() ;
 
717
 
 
718
    // declare the flipped array we use for the first pass search
 
719
    $searchArray = array() ;
 
720
    
 
721
    // loop through the addresses, hash the components, and build the hash-keyed search array
 
722
    foreach($addresses as $index => $addressComponents)
 
723
    {
 
724
      $hash = $this->hashAddress($addressComponents[0]) ;
 
725
      $addrHashes[$index] = $hash ;
 
726
    }
 
727
 
 
728
    // return any found hashes
 
729
    $dbHashes = $this->getAddressIdsByHash(array_unique(array_values($addrHashes))) ;
 
730
 
 
731
    // loop through the generated hashes and build a couple of arrays
 
732
    foreach ($addrHashes as $addrIndex => $addrHash)
 
733
    {
 
734
      // if we found an address id already, our life is much easier
 
735
      if (array_key_exists($addrHash, $dbHashes))
 
736
      {
 
737
        // for each of the addresses with that ID, build our results set and
 
738
        // unset the value from the stuff left to be processed (we're going to use that later!)
 
739
        $results[$addrIndex] = $dbHashes[$addrHash] ;
 
740
        unset($addresses[$addrIndex]) ;
 
741
      }
 
742
      else
 
743
      {
 
744
        // if that didn't work out for us we move the hash to the addresses array for pass-through
 
745
        $addresses[$addrIndex][2] = $addrHash ;
 
746
      }
 
747
 
 
748
      // either way, we've already processed this address hash, so we can release it
 
749
      unset($addrHashes[$addrIndex]) ;
 
750
    }
 
751
 
 
752
    // just 'cause this is going to be a very memory-hungry method, we'll unset the hashes too
 
753
    unset($dbHashes) ;
 
754
 
 
755
    // now that we have all of the 'existing' addresses, let's build the new ones
 
756
    $newAddresses = $this->setNewAddresses($addresses) ;
 
757
    $successes = array_shift($newAddresses) ;
 
758
 
 
759
    // we don't need this anymore!
 
760
    unset($newAddresses) ;
 
761
 
 
762
    foreach ($successes as $index => $addrId)
 
763
    {
 
764
      // add our successes to the final results set
 
765
      $results[$index] = $addrId ;
 
766
 
 
767
      // release the address from our initial input array
 
768
      unset($addresses[$index]) ;
 
769
 
 
770
      // release it from the successes array while we're at it
 
771
      unset($successes[$index]) ;
 
772
    }
 
773
 
 
774
    // and finally we return our results, both the successes and the failures
 
775
    return array($results, array_keys($addresses)) ;
 
776
  }
 
777
 
 
778
  /**
 
779
   * A big honkin' method to create a new address and, if also necessary, the address elements that
 
780
   * don't yet exist to support the address.
 
781
   *
 
782
   * NOTE: This method does not fail fast. Failed address inserts are returned by index as part of
 
783
   * the results set.
 
784
   *
 
785
   * @param array $addresses This multi-dimensional array of address data is keyed by an arbitrary
 
786
   * index. The values of each index are: an array of address components, keyed by element id, the
 
787
   * default address standard of this address, and the address hash of the components.
 
788
   * <code>
 
789
   * array(
 
790
   *    [$index] => array(
 
791
   *      [0] => array( [$elementId] => $valueString, ...),
 
792
   *      [1] => $addressStanardId,
 
793
   *      [2] => $addressHash
 
794
   *    ),
 
795
   *    ...
 
796
   * )
 
797
   * </code>
 
798
   * @param Doctrine_Connection $conn A doctrine connection object.
 
799
   * @return array A two dimensional array. The first array element ([0]), returns an array of
 
800
   * address indexes and the newly inserted addressIds. The second array element [1], returns all
 
801
   * address indexes that could not be inserted.
 
802
   * <code>
 
803
   * array(
 
804
   *  [0] => array( [$addressIndex] => [$addressId], ... )
 
805
   *  [1] => array( $addressIndex, ... )
 
806
   * )
 
807
   * </code>
 
808
   */
 
809
  protected function setNewAddresses($addresses, Doctrine_Connection $conn = NULL)
 
810
  {
 
811
    // we'll use this like a cache and check against it with each successive execution
 
812
    $valuesCache = array() ;
 
813
 
 
814
    // declare our results array
 
815
    $results = array() ;
 
816
 
 
817
    // pick up the default connection if one is not passed
 
818
    if (is_null($conn)) { $conn = Doctrine_Manager::connection() ; }
 
819
 
 
820
    // loop through our addresses and the components
 
821
    foreach ($addresses as $index => $components)
 
822
    {
 
823
      // we do this so we only have to call rollback / unset once, plus it's nice to have a bool to
 
824
      // check on our own
 
825
      $err = FALSE ;
 
826
 
 
827
      // if for whatever reason we're not passed a standard, pick up the default
 
828
      if (! isset($components[1])) { $components[1] = $this->_returnStandardId ; }
 
829
 
 
830
 
 
831
      // similarly, we want to wrap this whole sucker in a transaction
 
832
      $conn->beginTransaction() ;
 
833
 
 
834
      // build a results cache so we commit entire addresses at once, not just individual elements
 
835
      $resultsCache = array() ;
 
836
 
 
837
      foreach($components[0] as $elementId => $value)
 
838
      {
 
839
        // if we've already picked up this address value, GREAT! just load it from our
 
840
        // cache and keep going!
 
841
        if (isset($valuesCache[$elementId][$value]))
 
842
        {
 
843
          $resultsCache[$elementId] = $valuesCache[$elementId][$value] ;
 
844
        }
 
845
        else
 
846
        {
 
847
          // since we didn't find it in cache, we'll try to grab it from the db
 
848
          $valueId = $this->getAddressValueId($elementId, $value) ;
 
849
 
 
850
          // unfortunately, if we didn't get value we've got to add it!
 
851
          if (empty($valueId))
 
852
          {
 
853
 
 
854
            $addrValue = new agAddressValue();
 
855
            $addrValue['address_element_id'] = $elementId ;
 
856
            $addrValue['value'] = $value ;
 
857
            try
 
858
            {
 
859
              // save the address
 
860
              $addrValue->save($conn) ;
 
861
              $valueId = $addrValue->getId() ;
 
862
              
 
863
              // and since that went right, add it to our results arrays
 
864
              $valuesCache[$elementId][$value] = $valueId ;
 
865
              $resultsCache[$elementId] = $valueId ;
 
866
            }
 
867
            catch(Exception $e)
 
868
            {
 
869
              // if we run into a problem, set this once rollback will roll it all back at the end
 
870
              $err = TRUE ;
 
871
              break ;
 
872
 
 
873
            }
 
874
          }
 
875
        }
 
876
      }
 
877
 
 
878
      // now we attempt to insert the new address_id with all of our value bits, again only useful
 
879
      // if we've not already had an error
 
880
      if (! $err)
 
881
      {
 
882
        // attempt to insert the actual address
 
883
        $newAddr = new agAddress() ;
 
884
        $newAddr['address_standard_id'] = $components[1] ;
 
885
        $newAddr['address_hash'] = $components[2] ;
 
886
 
 
887
        try
 
888
        {
 
889
          // save the address
 
890
          $newAddr->save($conn) ;
 
891
          $addrId = $newAddr->getId() ;
 
892
        }
 
893
        catch(Exception $e)
 
894
        {
 
895
          // if we run into a problem, set this once rollback will roll it all back at the end
 
896
          $err = TRUE ;
 
897
        }
 
898
      }
 
899
 
 
900
      // the final step!!! inserting into agAddressMjAgAddressValue
 
901
      foreach ($resultsCache as $rElem => $rValueId)
 
902
      {
 
903
        // if we at any point pick up an error, don't bother
 
904
        if ($err) { break ; }
 
905
 
 
906
        $newAmav = new agAddressMjAgAddressValue() ;
 
907
        $newAmav['address_id'] = $addrId ;
 
908
        $newAmav['address_value_id'] = $rValueId ;
 
909
 
 
910
        try
 
911
        {
 
912
          // save the address
 
913
          $newAmav->save($conn) ;
 
914
        }
 
915
        catch(Exception $e)
 
916
        {
 
917
          // if we run into a problem, set this once rollback will roll it all back at the end
 
918
          $err = TRUE ;
 
919
        }
 
920
      }
 
921
 
 
922
      // if there's been an error, at any point, we rollback any transactions for this address
 
923
      if ($err)
 
924
      {
 
925
        $conn->rollback() ;
 
926
      }
 
927
      else
 
928
      {
 
929
        // most excellent! no erors at all, so we commit... finally!
 
930
        $conn->commit() ;
 
931
 
 
932
        // commit our results to our final results array
 
933
        $results[$index] = $addrId ;
 
934
 
 
935
        // release the value on our input array
 
936
        unset($addresses[$index]) ;
 
937
      }
 
938
    }
 
939
 
 
940
    // Whew!! Now that that's over, let's just return our two results
 
941
    return array($results, array_keys($addresses)) ;
 
942
  }
 
943
 
 
944
  /**
 
945
   * Simple method to retrieve an address value / element.
 
946
   *
 
947
   * @param integer $elementId The element id being queried.
 
948
   * @param string $value String value being searched for.
 
949
   * @param Doctrine_Connection $conn A doctrine connection object.
 
950
   * @return integer|array This is a little funny behaviour or HYDRATE_SINGLE_SCALAR's part. If no
 
951
   * value is returned from the query it actually returns an empty array instead of a NULL (which
 
952
   * would seem more appropriate). Use empty() to check if there's an actual value.
 
953
   */
 
954
  public function getAddressValueId($elementId, $value, $conn = NULL)
 
955
  {
 
956
    $q = agDoctrineQuery::create()
 
957
      ->select('av.id')
 
958
        ->from('agAddressValue av')
 
959
        ->where('av.address_element_id = ?', $elementId)
 
960
          ->andWhere('av.value = ?', $value) ;
 
961
 
 
962
    if (! is_null($conn)) { $q->setConnection($conn) ; }
 
963
 
 
964
    return $q->execute(array(), Doctrine_Core::HYDRATE_SINGLE_SCALAR);
470
965
  }
471
966
 
472
967
  /**