Laravel model with POINT/POLYGON etc. using DB::raw expressions - php

I have some models that use geospatial fields like POINT, POLYGON or MULTIPOLYGON. I would like to tell my model to process these attributes in a special way, for me to get the desired model attributes set.
Example:
Every regular Model::find() or other Eloquent method should apply some custom code before storing or after retrieving a database value.
$area->surface is a POLYGON field in MySQL, but in my model class I would like to handle $area->surfare as an array of points.
On SELECT I would therefore like to 1) fetch the value using a raw expression to get a text representation of the value, and 2) go through some custom PHP code to convert the WKT string into an array.
On INSERT/UPDATE I would like to take the attribute value (an array) and 1) convert it into a WKT string, whereafter 2) it's written to the databse using a DB raw statement that stores the value.
I'd like to set this on a field-basis, not as special get/set functions for each field, and not in the controllers - because I have many geosptial fields.
Is there a way to achieve this in Laravel?
(A more abstract version of the same question, is how I can create code that manipulates attribute values for the actual SQL queries, rather than just some value-based manipulation via mutators & accessors)
UPDATE:
Looking deeper into the Laravel Doc and API, I found that maybe the Eloquent::newQuery() method is what I need to manipulate? Would that be used for any query regardless if SELECT, INSERT or UPDATE?

We have now solved this generically for all models by extending our base model with the following functionaly:
We define an array of attributes that hold geometric data.
We decide on a per-model-basis if we want this to be auto-loaded as text.
We change the default query builder to select the geometry attributes as text from the database.
Here is an excerpt from the base model we now use:
/**
* The attributes that hold geometrical data.
*
* #var array
*/
protected $geometry = array();
/**
* Select geometrical attributes as text from database.
*
* #var bool
*/
protected $geometryAsText = false;
/**
* Get a new query builder for the model's table.
* Manipulate in case we need to convert geometrical fields to text.
*
* #param bool $excludeDeleted
* #return \Illuminate\Database\Eloquent\Builder
*/
public function newQuery($excludeDeleted = true)
{
if (!empty($this->geometry) && $this->geometryAsText === true)
{
$raw = '';
foreach ($this->geometry as $column)
{
$raw .= 'AsText(`' . $this->table . '`.`' . $column . '`) as `' . $column . '`, ';
}
$raw = substr($raw, 0, -2);
return parent::newQuery($excludeDeleted)->addSelect('*', DB::raw($raw));
}
return parent::newQuery($excludeDeleted);
}

If mutators and accessor does not fit your needs, you can manipulate those attributes with Model Events.
Then you can execute code when some of the Eloquent events are fired: creating, created, updating, updated, saving, saved, deleting, deleted, restoring, restored.

For all who use postgres database, laravel-postgis can be use for geometric data

Related

TYPO3: How to create backend view with Standalone. (Custom Content Elements)

How can i create a backend view template but as HTML for custom content elements.
I know i have to use the Standalone class but i do not know how.
System: TYPO3 v9
Mode: Composer Mode
Target: Custom Content element
The first steps are described here perfectly: First steps
After that you will have to include some classes
namespace Vendor\YourExtensionName\Hooks\PageLayoutView;
use TYPO3\CMS\Backend\View\PageLayoutViewDrawItemHookInterface;
use TYPO3\CMS\Backend\View\PageLayoutView;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Fluid\View\StandaloneView;
use TYPO3\CMS\Core\Database\ConnectionPool;
Then you have the following:
class MyPreviewRenderer implements PageLayoutViewDrawItemHookInterface
{
/**
* Preprocesses the preview rendering of a content element of type "Your CType"
*
* #param \TYPO3\CMS\Backend\View\PageLayoutView $parentObject Calling parent object
* #param bool $drawItem Whether to draw the item using the default functionality
* #param string $headerContent Header content
* #param string $itemContent Item content
* #param array $row Record row of tt_content
*
* #return void
*/
public function preProcess(
PageLayoutView &$parentObject,
&$drawItem,
&$headerContent,
&$itemContent,
array &$row
)
{
}
Fetch objects
In the preProcess function we have to first fetch the objects. Do not forget. The &parentObjects is mapped to the tt_content table and the &row holds the information on the current tt_content entry and not the information of your CType which is saved on yourTable table. So we have to create an SQL query to get these information. To do so we have the following:
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('yourTable');
$foo = $queryBuilder
->select('*')
->from('yourTable')
->where(
$queryBuilder->expr()->eq('tt_content', $queryBuilder->createNamedParameter($row['uid'], \PDO::PARAM_INT)),
$queryBuilder->expr()->eq('hidden', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)),
$queryBuilder->expr()->eq('deleted', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)),
)
->execute()
->fetchAll();
What we did here, is to get all the objects that have a relation with the tt_content's uid. Note that the tt_content is a field in the yourTable which holds the uid of the tt_content's table entry.
Define template
Now that we have all the objects in hand, we have to define the path to the backend template.
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
$standaloneView = $objectManager->get(StandaloneView::class);
$templatePath = GeneralUtility::getFileAbsFileName('EXT:yourExtension/Resources/Private/Templates/Backend/Backend.html');
Now we have to assign the objects to a variable in order to pass them to the fluid template.
$standaloneView->setFormat('html');
$standaloneView->setTemplatePathAndFilename($templatePath);
$standaloneView->assignMultiple([
'foo' => $foo,
'cTypeTitle' => $parentObject->CType_labels[$row['CType']],
]);
And finally render the template:
$itemContent .= $standaloneView->render();
$drawItem = false;
Additional information:
It is recommended, that you wrap all your code (inside the preProcess function) in the CType that it belongs to. To do so you ll have to wrap it like this:
if ($row['CType'] === 'yourCTypeName') {
//...
}

Using column names when generating JSON in Propel

When using toJSON() on an ObjectCollection the names of the properties are always based on the PHP-names. For instance:
For the column type_name the JSON property becomes TypeName, etc.
Is there a way to make Propel use the name of the field/column instead?
If you don't mind using json_encode, try using the object's toArray() with arguments:
use Map\AuditTableMap as TableMap;
$something = new Something();
$something->setSomeColumnValue("value");
echo json_encode($something->toArray(SomethingMap::TYPE_FIELDNAME));
Output:
{"some_column_value": "value"}
In other words, use the argument <ObjectName>Map::TYPE_FIELDNAME to output an array with column names.
The docs are amazing, but they're quite confusing to navigate. I found the following comment from one of the generated models in my project. This is for version 2.0#dev, which I'm using; note that it may differ in your version. (I'd suggest looking at the docs for more formal guidance, but you can take a peek at your models too.)
/**
* Exports the object as an array.
*
* You can specify the key type of the array by passing one of the class
* type constants.
*
* #param string $keyType (optional) One of the class type constants TableMap::TYPE_PHPNAME, TableMap::TYPE_CAMELNAME,
* TableMap::TYPE_COLNAME, TableMap::TYPE_FIELDNAME, TableMap::TYPE_NUM.
* Defaults to TableMap::TYPE_PHPNAME.
* #param boolean $includeLazyLoadColumns (optional) Whether to include lazy loaded columns. Defaults to TRUE.
* #param array $alreadyDumpedObjects List of objects to skip to avoid recursion
* #param boolean $includeForeignObjects (optional) Whether to include hydrated related objects. Default to FALSE.
*
* #return array an associative array containing the field names (as keys) and field values
*/
If you only want to strictly use the toJSON call, then you'll have to do some post-processing manipulation of the string, because the only option allowed with the toJSON method is to include or not include lazy-loaded columns.
$something = new Something();
$something->setSomeColumnValue("value");
$json = $something->toJSON();
$tableMap = \Propel::getDatabaseMap()->getTableMap('Something');
$columnMaps = $tableMap->getColumns();
$phpNames = array();
$columnNames = array();
foreach ($columnMaps as $columnMap) {
$phpNames[] = '"' . $columnMap->getPhpName() . '"';
$columnNames[] = '"' . $columnMap->getColumnName() . '"';
}
$json = str_replace($phpNames, $columnNames, $json);
One caveat to this code is that if the value matches one of your column names exactly, it will be replaced. The only way to eliminate this is to json_decode the JSON object and only replace the keys, but if you don't want to use json_encode, I don't suppose you'd want to use json_decode.
#Cezille07's answer is the most correct in this case. My answer is mainly to show how TableMap/ColumnMaps can be used for post-processing, which is something I didn't know about when I started with Propel.

Is it possible to map a column name in an Laravel Eloquent model?

I have a DB table with a column "visible" (true/false).
The Eloquent model already has a property visible, see this code snippet taken from Illuminate\Database\Eloquent\Model.
/**
* The attributes that should be visible in arrays.
*
* #var array
*/
protected $visible = array();
When printing out the model, the "visible" property is the already existing array instead of the column value. Is there a way to rename the column in the Model? I already tried this (found here: http://laravel.com/docs/4.2/eloquent#accessors-and-mutators)
public function setVisibleAttribute($value) {
$this->attributes['isvisible'] = $value;
}
public function getVisibleAttribute($value) {
$this->attributes['isvisible'];
}
I know I can solve the problem like this:
User::select('id', 'name', 'visible as isVisible')->get();
I was just wondering if there isn't a really built in mechanism.
Important note: the DB is also used by existing software so renaming it isn't really an option.

Using \Zend_Db_Table_Abstract::find($id). MySQL SET field returns string instead of (wanted) int

Basic question How can I fetch the 'type' column as integer value from inside the table mapper?
I have a PHP Zend Framework 1.12 application running a website. Inside MySQL are multiple tables with multiple columns.
Inside two tables I use the SET type. The column is named 'type' and as 'set('LOCAL','EXTERNAL')'. Don't mix up this field type with the ENUM please!
So far no problems, querying the table and fetching the type column as INT or STRING is not a problem:
$Sql = $Db->select()->from('tablename', ['type_as_int' => new \Zend_Db_Expr('type+0')]); //returns INT (if both are selected: 3)
$Sql = $Db->select()->from('tablename', ['type']); //returns STRING (if both are selected: LOCAL,EXTERNAL)
But, in this application also has table mappers that extend Zend_Db_Table_Abstract.
Inside the mapper resides the 'find()' method. Default built in into the abstract to find records by their primary key.
But.. When I use the object to fetch a record , I find the following response inside my populate method:
array([type] => LOCAL,EXTERNAL)
Querying it by hand (and defining the columns myself) would be an options ($this->select()->from...), but isn't there a more elegant way?
(I know that I am using an older version of ZF, but upgrading would cost too much time at this moment.)
After the bounty was started I noticed that there wasn't a really simple anwer, so I began looking deeper into Zend Framework 1.12 and the mapper objects that I use.
I noticed that the 'find()' method just uses the primary key columns to build a query.
So starting with that knowledge I built my own 'find()' method which resides in the 'abstract model mapper' and uses the 'find()' mapper inside the class that extends \Zend_Db_Table_Abstract
/* sample abstract mapper class with find */
abstract class MapperAbstract {
/*
* Zend db table abstract object
* #var \Zend_Db_Table_Abstract
*/
private $DbTable;
public function find($id, $Model) {
$Select = $this->$DbTable->select(\Zend_Db_Table_Abstract::SELECT_WITH_FROM_PART);
//Fetch record and populate model if we got
//a result
$Row = $this->$DbTable->fetchRow($Select);
//do some extra shizzle
if ($Row !== null) {
return $Model->populate((array)$Row);
}
return;
}
}
Now I need to add a method that overrides the default columns.
So I created a method called 'overrideColumns()' that return an array filled with column names that need to be selected or must be overriden.
/**
* Returns array with columns that need to be overridden
* or selected as extra
* #return array
*/
public function overrideColumns() {
return ['type' => new \Zend_Db_Expr('type+0')];
}
And from that point I only needed to adjust the $Select query so it would use the 'overrideColumns()' method.
So the full class becomes something like:
/* sample abstract mapper class with find */
abstract class MapperAbstract {
/*
* Zend db table abstract object
* #var \Zend_Db_Table_Abstract
*/
private $DbTable;
/**
* Returns array with columns that need to be overridden
* or selected as extra
* #return array
*/
private function overrideColumns() {
return ['type' => new \Zend_Db_Expr('type+0')];
}
public function find($id, $Model) {
$Select = $this->DbTable->select(\Zend_Db_Table_Abstract::SELECT_WITH_FROM_PART);
//Check if we need to override columns in the select query
$overrideColumns = $this->getOverrideColumns();
if (!empty($overrideColumns)) {
$Select->columns($overrideColumns); //overrides the columns
}
//Add where clause to the query
//I know there can also be a compound primary key, but
//I'm just ignoring that in this example
$Select->where($this->DbTable->getPrimaryKeyColumn() . ' = ?', $id);
//doing some extra shizzle
//that is not important when I want to explain stuff
//Fetch record and populate model if we got a result
$Row = $this->DbTable->fetchRow($Select);
if ($Row !== null) {
return $Model->populate((array)$Row);
}
return;
}
}
So.. after a while I found the answer I was looking for, without having to declare all columns.

Zend Framework Model, Active Record pattern alternative

I'm writing some code that allows users to read reports on a site, using AJAX calls to dynamically load only what is requested, instead of the entire 15+MB report.
I'm writing a Model to access all the report data from the database, and I don't want to use the Active Record pattern. I'm following the idea of "A Model HAS a table, instead of IS-A table", since this model will be accessing 5 different tables, and there are some complex MySQL JOIN's between these tables.
What is a good design pattern to follow in Zend Framework for this, examples?
UPDATED on 2012-12-05 # 12:14PM EST
I'm currently working for a Market Research Report company. Without using actual function names, or revealing any meaningful details of the code, here are the basics:
readreportAction() does:
get the report meta data
get the report "table of contents"
readsectionAction() does:
get the report text, only a part of it
get the embedded tabular data
get the figures / images
get the footnotes
format the report text
reportpdfAction() does the exact same thing as readreportAction() and readsectionAction(), except all at one time. I'm trying to conceptualize a way to NOT copy + paste this code / programming logic. A data mapper seems to solve this.
I would recommend the Data Mapper pattern.
Everything you said makes sense and this pattern fits. Your model should not know or care how it is persisted. Instead the mapper does what it suggests - maps your model to your database. One of the things I like about this approach is it encourages people to think about the model in terms of an object, not a relational database table, as often happens with active record patterns and table row gateways.
Your object, unless very simple, typically will not reflect the structure of a database table. This lets you write good objects and then worry about the persistence aspects afterward. Sometimes more manual in that your mapper will need to deal with the complex joins, probably requiring writing some code or SQL, but the end result is it does just what you want and nothing more. No magic or conventions required if you don't want to leverage them.
I've always though these articles do a good job of explaining some of the design patterns that can be used well in ZF: http://survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors#zfbook.implementing.the.domain.model.entries.and.authors.exploring.the.entry.data.mapper
UPDATE:
Well you mapper might extend from an interface similar to this:
<?php
interface Mapper_Interface
{
/**
* Sets the name of the entity object used by the mapper.
*/
public function setObjectClass($class);
/**
* Sets the name of the list class used by the mapper.
*/
public function setObjectListClass($listClass);
/**
* Get the name of the object class used by the mapper.
*
*/
public function getObjectClass();
/**
* Get the name of the object list class used by the mapper.
*
* #return string
*/
public function getObjectListClass();
/**
* Fetch one row.
*
* #param array $where Criteria for the selection.
* #param array [$order = array()] Optionally the order of results
* #return Object_Abstract
* #throws Mapper_Exception
*/
public function fetchRow($where, $order = array());
/**
* Fetch all records. If there is no underlying change in the persisted data this should
* return a consistant result.
*
* #param string|array|Zend_Db_Table_Select $where OPTIONAL An SQL WHERE clause or Zend_Db_Table_Select object.
* #param string|array $order OPTIONAL An SQL ORDER clause.
* #param int $count OPTIONAL An SQL LIMIT count.
* #param int $offset OPTIONAL An SQL LIMIT offset.
* #return Object_List_Abstract
* #throws Mapper_Exception
*/
public function fetchAll($where = null, $order = null, $count = null, $offset = null);
/**
* Deletes one or more object.
*
* #param array|string $where Criteria for row deletion.
* #return integer $affectedRows
* #throws Mapper_Exception
*/
public function delete($where);
/**
* Saves a record. Either updates or inserts, as required.
*
* #param $object Object_Abstract
* #return integer $lastInsertId
* #throws Mapper_Exception
*/
public function save($object);
}
And you would interact with the mapper like:
$fooObjectMapper = new Foo_Mapper;
$fooObjectList = $fooObjectMapper->fetchAll();
var_dump($fooObjectList->first());
or
$fooObjectMapper = new Foo_Mapper;
$fooObject = $fooObject->fetch(array('id = ?' => 1));
$fooObject->setActive(false);
$fooObjectMapper->save($fooObject);
I usually write a mapper abstract for any 'PDO' enabled databases. One of the attributes of that concrete mapper is then the Zend_Db_Adapter to issue commands against. Makes for a flexible solution, easy to use mock data sources in testing.
First it looks like you need to make a little bit more of a conceptual leap. With the data mapper pattern it helps to think in terms of objects instead of database tables. I found these two articles helpful when I needed to make the leap.
http://phpmaster.com/building-a-domain-model/
http://phpmaster.com/integrating-the-data-mappers/
That being said ZF 1 has some very useful tools for building a data mapper/domain model.
The convention in ZF 1 is for each table you are working with to be accessible through the Zend_Db_Table api. The simplest way I've found is to just use the DbTable resource for each table. You could also use the Zend_Db::factory or new Zend_Db_Table('tableName') or any other method that appeals to you.
This example is based on a mp3 song track.
//in effect this is the database adapter for database table 'track', This is $tableGateway used later.
<?php
class Application_Model_DbTable_Track extends Zend_Db_Table_Abstract
{
//name of database table, required to be set if name of class does not match name of table
protected $_name = 'track';
//optional, column name of primary key
protected $_primary = 'id';
}
there are several ways to attach a table to the Db adapter and the Zend_Db_Table api, I just find this method simple to implement and it makes setting up a mapper simple as well.
The mapper class is the bridge between the data source and your object (domain entity). The mapper interacts with the api for Zend_Db_Table in this example.
A really important point to understand: when using classes that extend Zend_Db_Table_Abstract you have all the basic functionality of the Zend_Db component at your disposal. (find(),fetchall(), fetchRow(), select() ...)
<?php
class Music_Model_Mapper_Track extends Model_Mapper_Abstract
{
//the mapper to access the songs artist object
protected $artistMapper;
//the mapper to access to songs album object
protected $albumMapper;
/**
* accepts instance of Zend_Db_Table_Abstract
*
* #param Zend_Db_Table_Abstract $tableGateway
*/
public function __construct(Zend_Db_Table_Abstract $tableGateway = null)
{
//at this point I tend to hardcode $tablegateway but I don't have to
$tableGateway = new Application_Model_DbTable_Track();
parent::__construct($tableGateway);
//parent sets the $tablegateway variable and provides an abstract requirement
//for createEntity(), which is the point of this class
}
/**
* Creates concrete object of Music_Model_Track
*
* #param object $row
* #return Music_Model_Track
*/
public function createEntity($row)
{
$data = array(
'id' => $row->id,
'filename' => $row->filename,
'format' => $row->format,
'genre' => $row->genre,
'hash' => $row->hash,
'path' => $row->path,
'playtime' => $row->playtime,
'title' => $row->title,
'track_number' => $row->track_number,
'album' => $row->album_id,//foriegn key
'artist' => $row->artist_id//foriegn key
);
//instantiate new entity object
return new Music_Model_Track($data);
}
/**
* findById() is proxy for find() method and returns
* an entity object.
*
* #param type $id
* #return object Model_Entity_Abstract
*/
public function findById($id)
{
//instantiate the Zend_Db_Select object
$select = $this->getGateway()->select();
$select->where('id = ?', $id);
//retrieve one database table row
$row = $this->getGateway()->fetchRow($select);
//create one entity object Music_Model_Track
$entity = $this->createEntity($row);
//return one entity object Music_Model_Track
return $entity;
}
//truncated
}
All that has gone before is for the express purpose of building the following object:
<?php
class Music_Model_Track extends Model_Entity_Abstract
{
/**
* $id, __set, __get and toArray() are implemented in the parent
*/
protected $album;
protected $artist;
protected $filename;
protected $format;
protected $genre;
protected $hash;
protected $path;
protected $playtime;
protected $title;
protected $track_number;
//artist and album mappers
protected $albumMapper = null;
protected $artistMapper = null;
//these are the important accessors/mutators because they convert a foreign key
//in the database table to an entity object.
public function getAlbum()
{
//if the album object is already set, use it.
if(!is_null($this->album) && $this->album instanceof Music_Model_Album) {
return $this->album;
} else {
//else we make a new album object
if(!$this->albumMapper) {
$this->albumMapper = new Music_Model_Mapper_Album();
}
//This is the album object we get from the id in our reference array.
return $this->albumMapper->findById($this->getReferenceId('album'));
}
}
//same as above only with the artist object.
public function getArtist()
{
if(!is_null($this->artist) && $this->artist instanceof Music_Model_Artist) {
return $this->artist;
} else {
if(!$this->artistMapper) {
$this->artistMapper = new Music_Model_Mapper_Artist();
}
return $this->artistMapper->findById($this->getReferenceId('artist'));
}
}
//the setters record the foriegn keys recorded in the table row to an array,
//this allows the album and artist objects to be loaded only when needed.
public function setAlbum($album)
{
$this->setReferenceId('album', $album);
return $this;
}
public function setArtist($artist)
{
$this->setReferenceId('artist', $artist);
return $this;
}
//standard setter and getters truncated...
}
so when using the track object you would get album or artist info like:
//this would be used in a controller most likely.
$mapper = new Music_Model_Mapper_Track();
$track = $mapper->findById('1');
//all of the information contained in the album or artist object is
//available to the track object.
//echo album title, year or artist. This album object also contains the artist object
//so the artist object would be available in two ways.
echo $track->album->title; //or
echo $track->artist->name;
echo $track->album->artist->name;
echo $track->getAlbum()->getArtist()->getName();
So what you really need to decide is how you want to structure your application. What I see as obvious may not be an option you wish to implement. A lot of the answers to your questions depend on exactly how these resources are to be used.
I hope this helps you at least a little bit.
You could consider using Doctrine 2. It's an ORM that does not use the ActiveRecord pattern.
In Doctrine, your models (entities) are all just normal PHP objects with zero knowledge of the database. You use mapping (xml, yaml or annotations) to tell Doctrine how they appear in the database, and the Entity Manager and repositories are used as a gateway for persisting entities or doing other database actions.

Categories