Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Getting things to work in eloquent observer methods? #115

Open
vpratfr opened this issue Feb 22, 2020 · 1 comment · May be fixed by #135
Open

Getting things to work in eloquent observer methods? #115

vpratfr opened this issue Feb 22, 2020 · 1 comment · May be fixed by #135

Comments

@vpratfr
Copy link

vpratfr commented Feb 22, 2020

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();
}
@vpratfr
Copy link
Author

vpratfr commented Feb 22, 2020

I have found another work around which does not require any change to the package.

        $forecast->geometries['zone'] = $coast->zone;
        $forecast->zone = new SpatialExpression($coast->zone);

By setting both geometry and regular attribute, I manage to keep both the QueryBuilder and the SpatialTrait happy.

Idea was given by reading pull request #71

@grimzy grimzy linked a pull request Mar 9, 2020 that will close this issue
@vpratfr vpratfr linked a pull request Apr 6, 2020 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants