1

First off, here's the SO question+answer where I got my information - laravel 4 saving ip address to model.

So my table will potentially have millions of row, therefore to keep storage low I opted for option 2 - using the Schema builder's binary() column and converting/storing IPs as binary with the help of Eloquents' accessors/mutators.

Here's my table:

Schema::create('logs', function ( Blueprint $table ) {
    $table->increments('id');
    $table->binary('ip_address'); // postgresql reports this column as BYTEA
    $table->text('route');
    $table->text('user_agent');
    $table->timestamp('created_at');
});

The first problem I ran into was saving the IP address. I set an accessor/mutator on my model to convert the IP string into binary using inet_pton() and inet_ntop(). Example:

public function getIpAddressAttribute( $ip )
{
    return inet_ntop( $ip );
}

public function setIpAddressAttribute( $ip )
{
    $this->attributes['ip_address'] = inet_pton( $ip );
}

Trying to save an IP address resulted in the whole request failing - nginx would just return a 502 bad gateway error.

OK. So I figured it had to be something with Eloquent/PostgreSQL not playing well together while passing the binary data.

I did some searching and found the pg_escape_bytea() and pg_unescape_bytea() functions. I updated my model as follows:

public function getIpAddressAttribute( $ip )
{
    return inet_ntop(pg_unescape_bytea( $ip ));
}

public function setIpAddressAttribute( $ip )
{
    $this->attributes['ip_address'] = pg_escape_bytea(inet_pton( $ip ));
}

Now, I'm able to save an IP address without a hitch (at least, it doesn't throw any errors).

The new problem I'm experiencing is when I try to retrieve and display the IP. pg_unescape_bytea() fails with pg_unescape_bytea() expects parameter 1 to be string, resource given.

Odd. So I dd() $ip in the accessor, the result is resource(4, stream). Is that expected? Or is Eloquent having trouble working with the column type?

I did some more searching and found it's possible that pg_unescape_bytea() is not properly unescaping the data - https://bugs.php.net/bug.php?id=45964.

After much headbanging and hairpulling, it became apparent that I might be approaching this problem from the wrong direction, and need some fresh perspective.

So, what am I doing wrong? Should I be using Postgres' BIT VARYING instead of BYTEA by altering the column type --

DB::statement("ALTER TABLE logs ALTER COLUMN ip_address TYPE BIT VARYING(16) USING CAST(ip_address AS BIT VARYING(16))");`

-- Or am I merely misusing pg_escape_bytea / pg_unescape_bytea?

All help is appreciated!

Community
  • 1
  • 1
Andy
  • 875
  • 9
  • 15

1 Answers1

1

Like already said in the comments to your question: in your specific case you should use the corresponding PostgreSQL data type and handling will be much easier. Compared to MySQL you will have a lot of other types in PostgreSQL (like JSON), there is a PostgreSQL data type overview page for further reference.

That said, other people could stumble upon a similar problem with bytea fields. The reason why you got Resource instead of string was that PostgreSQL treats bytea fields as streams. A very naïve approach would be to first get the stream and then to return the data:

public function getDataAttribute($value)
{
    // This will kill your server under high load with large values.
    $data =  fgets($value);

    return pg_unescape_bytea($data);
}

You can imagine that this could be a problem where multiple people try to get big files (currently hundreds of MiB or a couple of GiB) where large data objects would need a lot of memory on the server (this could even get a problem on mobile devices without swap). In this case you should work with streams on the server and the client and just fetch the data on the client you really need.

Rafael Bugajewski
  • 1,702
  • 3
  • 22
  • 37