Created
May 3, 2018 06:27
-
-
Save imanilchaudhari/3cfcd7d3355b30a3ba1200ae781350a8 to your computer and use it in GitHub Desktop.
Yii2 active record compatible with geometrical data types.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace common\components; | |
use Yii; | |
use yii\db\Expression; | |
use yii\db\ActiveRecord as YiiActiveRecord; | |
use yii\base\InvalidCallException; | |
/** | |
* Class providing additional features for yii\db\ActiveRecord. Add support for JSON and spatial data types. | |
* | |
* @since 0.0.1 | |
*/ | |
class ActiveRecord extends YiiActiveRecord | |
{ | |
/** | |
* @since 0.0.1 | |
* @var float Property for distance between geometrical points | |
*/ | |
public $_d; | |
protected $_saved = []; | |
/** | |
* {@inheritDoc} | |
*/ | |
public static function find() { | |
return Yii::createObject(ActiveQuery::className(), [get_called_class()]); | |
} | |
/** | |
* Determine whether given `$column` is of spatial data type POINT | |
* | |
* @since 0.0.1 | |
* @param string $column The column to check | |
* @return boolean | |
*/ | |
public static function isPoint($column) { | |
return $column ? ($column->dbType == 'point') : false; | |
} | |
/** | |
* Determine whether given `$column` is of spatial data type POLYGON | |
* | |
* @since 0.0.1 | |
* @param string $column The column to check | |
* @return boolean | |
*/ | |
public static function isPolygon($column) { | |
return $column ? ($column->dbType == "polygon") : false; | |
} | |
/** | |
* Determine whether given `$column` is of data type JSON | |
* | |
* @since 0.0.1 | |
* @param string $column | |
* @return boolean | |
*/ | |
public static function isJson($column) { | |
return $column ? ($column->dbType == "json") : false; | |
} | |
/** | |
* Convert WKT to point value. | |
* | |
* @since 0.0.1 | |
* @param string $val WKT to convert | |
* @return Expression | |
* @link https://postgis.net/docs/ST_PointFromText.html | |
*/ | |
protected function toDB($val) { | |
$driver = $this->db->driverName; | |
$exp = $val; | |
if ($driver === 'mysql') { | |
$pnt = str_replace(',', ' ', $val); | |
$exp = new Expression("ST_PointFromText('POINT($pnt)')"); | |
} | |
return $exp; | |
} | |
/** | |
* Convert geometric values back to more readable form. | |
* | |
* For example, converts DB value of `POINT(56.7 53.34)` to `56.7,53.34`. In case of POLYGON, converts `POLYGON((0 0,0 3,3 0,0 0),(1 1,1 2,2 1,1 1))` to | |
* ```php | |
* [ | |
* [ | |
* [ | |
* [x] => 0, | |
* [y] => 0 | |
* ], | |
* [ | |
* [x] => 0, | |
* [y] => 3 | |
* ], | |
* [ | |
* [x] => 3, | |
* [y] => 0 | |
* ], | |
* [ | |
* [x] => 0, | |
* [y] => 0 | |
* ] | |
* ], | |
* [ | |
* [ | |
* [x] => 1, | |
* [y] => 1 | |
* ], | |
* [ | |
* [x] => 1, | |
* [y] => 2 | |
* ], | |
* [ | |
* [x] => 2, | |
* [y] => 1 | |
* ], | |
* [ | |
* [x] => 1, | |
* [y] => 1 | |
* ] | |
* ] | |
* ] | |
* ``` | |
* | |
* @todo Improve regex patterns | |
* @since 0.0.1 | |
* @param string $exp The value to convert. | |
* @param string $geometry The type of geometry. Defaults to `point`. | |
* @return mixed | |
*/ | |
protected function toAttr($exp, $geometry = "point") { | |
$driver = $this->db->driverName; | |
$val = $exp; | |
if ($geometry === "point" && preg_match('#\((.*?)\)#', strtoupper($exp), $matches)) { | |
$val = $matches[1]; | |
if ($driver === 'mysql') | |
$val = str_replace(' ', ',', $val); | |
} | |
if ($geometry === "polygon" && preg_match("/^POLYGON\(\((.*?)\)+\)$/", strtoupper($exp), $matches)) { | |
$val = $matches[1]; | |
if ($driver === "mysql" && strpos($val, "),(") !== false) { | |
$parts = explode("),(", $val); | |
array_walk($parts, [$this, "demolishPolygon"]); | |
$val = $parts; | |
} | |
} | |
return $val; | |
} | |
/** | |
* Helper method to convert geometrical data to more workable form. | |
* | |
* @since 0.0.1 | |
* @param string $item | |
* @param string $key | |
*/ | |
protected function demolishPolygon(&$item, $key) { | |
$parts = explode(",", $item); | |
array_walk($parts, [$this, "coordinatesToArray"]); | |
$item = $parts; | |
} | |
/** | |
* Helper method to convert coordinates to array. | |
* | |
* @since 0.0.1 | |
* @param array $item The item to convert to array. | |
*/ | |
protected function coordinatesToArray(&$item) { | |
$parts = explode(" ", $item); | |
$item = ["x" => $parts[0], "y" => $parts[1]]; | |
} | |
protected function viewportToPolygon($viewport) { | |
return "POLYGON((".$viewport->west." ".$viewport->south.",".$viewport->west." ".$viewport->north.",".$viewport->east." ".$viewport->north.",".$viewport->east." ".$viewport->south.",".$viewport->west." ".$viewport->south."))"; | |
} | |
/** | |
* Convert specific attributes like JSON or spatial data to appropriate format to be saved in the DB. | |
* | |
* @since 0.0.1 | |
* @param object $insert | |
* @return object | |
*/ | |
public function beforeSave($insert) { | |
$r = parent::beforeSave($insert); | |
if ($r) { | |
$scheme = static::getTableSchema(); | |
foreach ($scheme->columns as $column) { | |
if (static::isPoint($column)) { | |
$field = $column->name; | |
$value = $this->getAttribute($field); | |
if ($value) { | |
$this->_saved[$field] = $value; | |
$exp = $this->toDB($value); | |
$this->setAttribute($field, $exp); | |
} | |
} | |
if (static::isJson($column)) { | |
$field = $column->name; | |
$value = $this->getAttribute($field); | |
//Check whether current attribute belongs to current scenario. If not, change it's value back to what it was. | |
//This is required due to the fact that in afterFind() all JSON type attributes are altered for the sake of | |
//easier usage. | |
if (in_array($field, array_keys($this->getDirtyAttributes())) && !in_array($field, $this->activeAttributes())) { | |
$this->{$field} = $this->oldAttributes[$field]; | |
continue; | |
} | |
//Remove the second part of comparison if something should fail. Added just for cases where input field is left empty. Otherwise would fail with exception. | |
if ($value || $value == "") { | |
$this->_saved[$field] = $value; | |
// $encoded = json_encode($value); | |
$this->setAttribute($field, $value); | |
} | |
} | |
} | |
} | |
return $r; | |
} | |
/** | |
* Restore specific attributes format as it was before saving to DB. | |
* | |
* @since 0.0.1 | |
* @param object $insert | |
* @param object $changedAttributes | |
*/ | |
public function afterSave($insert, $changedAttributes) { | |
foreach ($this->_saved as $field => $value) | |
$this->setAttribute($field, $value); | |
parent::afterSave($insert, $changedAttributes); | |
} | |
/** | |
* Convert specific attributes like JSON or spatial data types to more usable format. | |
* | |
* @since 0.0.1 | |
* @throws InvalidCallException | |
*/ | |
public function afterFind() { | |
parent::afterFind(); | |
$scheme = static::getTableSchema(); | |
foreach ($scheme->columns as $column) { | |
if (static::isPoint($column)) { | |
$field = $column->name; | |
$value = $this->getAttribute($field); | |
if ($value) { | |
if (YII_DEBUG && preg_match( '/[\\x80-\\xff]+/' , $value )) { | |
throw new InvalidCallException('Spatial attribute not converted.'); | |
} | |
$text = $this->toAttr($value); | |
$this->setAttribute($field, $text); | |
} | |
} | |
if (static::isPolygon($column)) { | |
$field = $column->name; | |
$value = $this->getAttribute($field); | |
if ($value) { | |
if (YII_DEBUG && preg_match( '/[\\x80-\\xff]+/' , $value )) { | |
throw new InvalidCallException('Spatial attribute not converted.'); | |
} | |
$text = $this->toAttr($value, "polygon"); | |
$this->setAttribute($field, $text); | |
} | |
} | |
//Add conversion for JSON data types | |
if (static::isJson($column)) { | |
$field = $column->name; | |
$value = $this->getAttribute($field); | |
if ($value) { | |
// $decoded = json_decode($value, true); | |
$this->setAttribute($field, $value); | |
} | |
} | |
} | |
} | |
public function getDistance() | |
{ | |
return $this->_d; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment