Skip to content

Getting things to work in eloquent observer methods? #115

Open
@vpratfr

Description

@vpratfr

I've been studying it under a lot of angles and I either have a problem at insertion time OR at query time. Here is what I have and what is going on. Following is a PR.

Models

I have 3 models, each properly setup using the spatial trait and declaring the spatial fields

class Region extends Model {
    // that one has a required zone attribute which is a Polygon
}

class Forecast extends Model {
    // that one has a required zone attribute which is a Polygon
}

class Boat extends Model {
    // that one has a required location attribute which is a Point
}

Creating a Forecast for a Region

From Laravel Nova, one can create a Forecast easily by specifying a region instead of directly specifying the zone. Filling the Forecast zone from the Region zone is done using an Eloquent observer with the creating method getting called before the model is saved.

class WeatherForecastCreationObserver
{
    private NovaRequest $request;

    public function __construct(NovaRequest $request)
    {
        $this->request = $request;
    }

    public function creating(WeatherForecast $forecast)
    {
        $coast = Coast::find($this->request->input('coast_id'));
        if ($coast !== null)
        {
            unset($forecast->coast_id);

            // This is throwing an SQLException, see below
            // $forecast->zone = $coast->zone;

            // I can however make it happy by wrapping it inside a SpatialExpression
            // Remember this tweak #1 as I will refer to it in next observer
            $forecast->zone = new SpatialExpression($coast->zone);
        }
    }
}

Here is the exception

Numeric value out of range: 1416 
Cannot get geometry object from data you send to the GEOMETRY field 
(SQL: insert into `weather_forecasts` (`timestamp`, `zone`) values 
(2020-02-18 00:00:00, (-16.3 16.28,-23 16.28,-23 14.45,-16.3 14.45,-16.3 16.28))) 
at /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Database/Connection.php:669)

I believe that inside the observer it is too late to convert fields and they get thrown at MySQL the way they are stored.

Alerting boats from a forecast alert

I have another observer which gets called to notify boats when an alarming forecast is saved in database. Its job is to query all boats inside forecast zone and send them a message.

class BroadcastWeatherAlertObserver
{
    public function saved(WeatherForecast $forecast)
    {
        $boatsAtRisk = Boat::within('location', $forecast->zone)->get();
    }
}

That method gets called after the Forecast is saved. Because of my tweak above, $forecast->zone passed to the within method is not a GeometryInterface. Hence the scopeWithin method fails:

Call to undefined method Grimzy\LaravelMysqlSpatial\Eloquent\SpatialExpression::toWkt() 
at /home/vagrant/code/vendor/grimzy/laravel-mysql-spatial/src/Eloquent/SpatialTrait.php:219)

However, we cannot assume it always will be a SpatialExpression, as we may have set the zone directly using a Polygon (first observer exiting early).

Any idea how to fix it all?

Proposed fix

My current solution and proposed PR is to add the method toWkt() to the SpatialExpression class:

public function toWkt()
{
    return $this->value->toWkt();
}

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions