In a MySQL database, I'm storing a VARBINARY(16) for an IPv4/IPv6 address. In order to display this on a form, I have created a DataTransformer in order to use the built-in inet_pton and inet_ntop functions.
I would like to display this in a Datatable, so searching by IP address would be possible. Unfortunately, by using my typical datatable setup, I get 500 server errors.
Through using browser developer tools, I'm able to determine that the results from the AJAX call is getting partial data, the actual IP address is listed as a resource, not the real data.
The error says: An unexpected value could not be normalized: NULL
In the stack trace, I see:
at Serializer ->normalize (resource, 'json', array())
in vendor/symfony/symfony/src/Symfony/Component/Serializer/Serializer.php at line 152
at Serializer ->normalize (array('id' => '5', 'ipAddress' => resource, 'sg_datatables_editable' => array(false), 'sg_datatables_actions' => array(array('network_ipaddress_show' => true))), 'json', array())
in vendor/symfony/symfony/src/Symfony/Component/Serializer/Serializer.php at line 152
at Serializer ->normalize (array(array('id' => '5', 'ipAddress' => resource, 'sg_datatables_editable' => array(false), 'sg_datatables_actions' => array(array('network_ipaddress_show' => true))), array('id' => '6', 'ipAddress' => resource, 'sg_datatables_editable' => array(false), 'sg_datatables_actions' => array(array('network_ipaddress_show' => true)))), 'json', array())
in vendor/symfony/symfony/src/Symfony/Component/Serializer/Serializer.php at line 152
at Serializer ->normalize (array('draw' => '1', 'recordsTotal' => '2', 'recordsFiltered' => '2', 'data' => array(array('id' => '5', 'ipAddress' => resource, 'sg_datatables_editable' => array(false), 'sg_datatables_actions' => array(array('network_ipaddress_show' => true))), array('id' => '6', 'ipAddress' => resource, 'sg_datatables_editable' => array(false), 'sg_datatables_actions' => array(array('network_ipaddress_show' => true))))), 'json', array())
in vendor/symfony/symfony/src/Symfony/Component/Serializer/Serializer.php at line 115
at Serializer ->serialize (array('draw' => '1', 'recordsTotal' => '2', 'recordsFiltered' => '2', 'data' => array(array('id' => '5', 'ipAddress' => resource, 'sg_datatables_editable' => array(false), 'sg_datatables_actions' => array(array('network_ipaddress_show' => true))), array('id' => '6', 'ipAddress' => resource, 'sg_datatables_editable' => array(false), 'sg_datatables_actions' => array(array('network_ipaddress_show' => true))))), 'json')
in vendor/sg/datatablesbundle/Sg/DatatablesBundle/Datatable/Data/DatatableQuery.php at line 743
Seeing the JSON calls, I implemented the JsonSerializable interface, but it seems that it's not being pulled that way.
While an option would be to place the inet_pton or inet_ntop in the entity directly, I believe I would lose validation.
What interface can I implement to allow Datatables to pull my IPAddress entity, and display the IP address properly?
Note that the data isn't translated from the entity (VARBINARY) to a readable string that the datatable requires. The stack trace shows that it's a "resource".
Added Code:
My Entity:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use JsonSerializable;
/**
* @ORM\Table(name="ip_address")
*/
class IPAddress implements JsonSerializable
{
/**
* @var int
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var mixed
* @ORM\Column(name="ip_address", type="binary", length=16, nullable=false, unique=true)
*/
private $ipAddress;
/**
* Set IP Address
* @param mixed $ipAddress
* @return IPAddress
*/
public function setIpAddress($ipAddress)
{
$this->ipAddress = $ipAddress;
return $this;
}
/**
* Get IP Address
* @return mixed
*/
public function getIpAddress()
{
return $this->ipAddress;
}
public function jsonSerialize()
{
return array(
'ipAddress' => inet_pton($this->ipAddress),
);
}
The Datatable class:
<?php
namespace AppBundle\Datatables;
use Sg\DatatablesBundle\Datatable\View\AbstractDatatableView;
use Sg\DatatablesBundle\Datatable\View\Style;
/**
* Class IPAddressDatatable
* @package AppBundle\Datatables
*/
class IPAddressDatatable extends AbstractDatatableView
{
/**
* {@inheritdoc}
*/
public function buildDatatable(array $options = array())
{
$this->features->set(array(
'auto_width' => true,
'defer_render' => false,
... other default features not listed ...
'delay' => 0,
'extensions' => array()
));
$this->ajax->set(array(
'url' => $this->router->generate('network_ipaddress_results'),
'type' => 'GET'
));
$this->options->set(array(
'display_start' => 0,
'defer_loading' => -1,
... other default options not listed ...
'use_integration_options' => false,
'force_dom' => false
));
$this->columnBuilder
->add('ipAddress', 'column', array(
'title' => 'IP Address',
'width' => '85%',
))
->add(null, 'action', array(
'title' => '',
'width' => '15%',
'actions' => array(
array(
'route' => 'network_ipaddress_show',
'route_parameters' => array(
'plan_id' => 'id'
),
'label' => $this->translator->trans('datatables.actions.show'),
'icon' => 'fi-eye icon-size-14',
'attributes' => array(
'rel' => 'tooltip',
'title' => $this->translator->trans('datatables.actions.show'),
'class' => 'tiny button',
'role' => 'button'
),
),
),
));
;
}
... getEntity and getName not shown for brevity ...
}
The Controller (at least the relevant parts):
/**
* Lists all IP Addresses.
*
* @Route("/", name="network_ipaddress_index")
* @Method("GET")
* @return IPAddressController
*/
public function indexAction()
{
$datatable = $this->get('app.datatable.ipaddress');
$datatable->buildDatatable();
return $this->render(':network:ipaddress_datatable.html.twig', array(
'datatable' => $datatable,
));
}
/**
* Returns a response, only used for datatables
*
* @Route("/results", name="network_ipaddress_results")
* @Method("GET")
* @return IPAddressController
*/
public function indexResultsAction()
{
$datatable = $this->get('app.datatable.ipaddress');
$datatable->buildDatatable();
// I believe that the entity data would have to be translated here into printable a format
$query = $this->get('sg_datatables.query')->getQueryFrom($datatable);
return $query->getResponse();
}
As it turns out, there is a closure available pre-rendering in the stwe/DatatablesBundle.
So, the function that is required in the above Datatables class:
/**
* {@inheritdoc}
*/
public function getLineFormatter()
{
$formatter = function($line){
$str = stream_get_contents($line['ipAddress']);
if( strlen( $str ) == 16 OR strlen( $str ) == 4 ){
$line['ipAddress'] = inet_ntop( pack( "A".strlen( $str ) , $str ) );
}
return $line;
};
return $formatter;
}