Home » Php » php – best way to run string functions over returned database object

php – best way to run string functions over returned database object

Posted by: admin July 12, 2020 Leave a comment

Questions:

I need to run various string functions over the data returned from the database before I send it to the view using Laravel 5.3. Basic stuff like str_replace().

Now maybe there’s a good way to setup Accessors on my model and somehow use the model on the landing page but I thought I would go a different route and just do this one query manually outside of the model.

So I have a view provider that successfully gets my data into the view. It looks like this:

class ViewLandingProvider extends ServiceProvider {
   public function boot() {
    // process when featured homepage element is present...
    View::composer('mybladetemplate', function ($view){

        $featuredProperties = DB::table('properties')
            ->where([
                ['featured_property', '=', '1'],
                ['supplier_id', '=', 123],
            ])
            ->orderBy('prop_id', 'desc')
            ->limit(6)
            ->get();

        // run str_replace!
        $featuredProperties->each(function($property){
            $property->prop_url=str_replace("http://domain.com/","http://www.domain.com/",$property->prop_url);
        });

        View::share('featuredProperties', $featuredProperties);
    });
  }
}

this then loops within a view and it all works nicely

 @if(isset($featuredProperties))
     @foreach ($featuredProperties as $property)
         <li>
             <a title="{{ $property->prop_name }}" href="{{ $property->prop_url }}"></a>
         </li>           
    @endforeach
@endif

As you can see in the example above, I have str_replace() running over the data collection using ->each() and that’s working to let me do a simple string replacement that I need to undertake.

Being Laravel though, I’m sure there’s some magic that could be pulled here to do this more intelligently.

So is there a way in the actual database request code that I can specify that a certain column to be returned should automatically have a function run over it ?

Just to clarify, I want to make these changes in the provider php rather than the view file and I want to do this outside of a model with Accessors.

How to&Answers:

I think you may be looking for a collection macro. You would register it in your AppServiceProvider like:

Collection::macro('formatPropUrl', function() {

    return collect($this->items)->map(function($property) {
        $property->prop_url=str_replace("http://domain.com/","http://www.domain.com/",$property->prop_url);

        return $property;
    });

});

Then for your query you could do:

$featuredProperties = DB::table('properties')
    ->where([
        ['featured_property', '=', '1'],
        ['supplier_id', '=', 123],
    ])
    ->orderBy('prop_id', 'desc')
    ->limit(6)
    ->get()
    ->formatPropUrl();

Answer:

You can write select query as:

$featuredProperties = DB::table('properties')
    ->where([
        ['featured_property', '=', '1'],
        ['supplier_id', '=', 123],
    ])
    ->select('*', DB::raw("replace(prop_url, 'http://domain.com/', 'http://www.domain.com/') as new_prop_url"))
    ->orderBy('prop_id', 'desc')
    ->limit(6)
    ->get();

And then in your view, you can do as:

@if(isset($featuredProperties))
   @foreach ($featuredProperties as $property)
       <li>
           <a title="{{ $property->prop_name }}" href="{{ $property->new_prop_url }}"></a>
       </li>           
  @endforeach
@endif

Answer:

This would be a good use case for the Presenter pattern. There are a few Laravel packages out there to help with this, but the most up-to-date one looks to be mccool/laravel-auto-presenter.

The idea behind the package is that you would create a Presenter object, which wraps the Model that needs to display information. The Presenter object contains all the logic for any data transformations required by the view.

This is untested, but an example implementation is shown below. Create an app\Presenters directory, and add the following file:

app\Presenters\PropertyPresenter.php

namespace App\Presenters;

use App\Property;
use McCool\LaravelAutoPresenter\BasePresenter;

class PropertyPresenter extends BasePresenter
{
    // this parameter MUST be named $resource
    public function __construct(Property $resource)
    {
        $this->wrappedObject = $resource;
    }

    // create functions for any properties you'd like to present
    public function prop_url()
    {
        return str_replace("http://domain.com/", "http://www.domain.com/", $this->wrappedObject->prop_url);
    }

    // you can also create functions for properties that don't actually
    // exist on the model. in view, access via $property->secure_prop_url
    public function secure_prop_url()
    {
        return str_replace("http:", "https:", $this->prop_url);
    }
}

Now, you need to modify your Property model to tell it about its presenter. It will need to implement the HasPresenter interface and define the getPresenterClass() method.

namespace App;

use App\Presenters\PropertyPresenter;
use Illuminate\Database\Eloquent\Model;
use McCool\LaravelAutoPresenter\HasPresenter;

class Property extends Model implements HasPresenter
{
    public function getPresenterClass()
    {
        return PropertyPresenter::class;
    }
}

Finally, all this logic depends on actually accessing the Property model inside the view. Your current logic is using DB::table('properties'), which will create an Collection of stdClass objects. This needs to be changed to actually use your Property model, so your view will get a Collection of Property models.

class ViewLandingProvider extends ServiceProvider {
    public function boot() {
        // process when featured homepage element is present...
        View::composer('mybladetemplate', function ($view) {

            $featuredProperties = \App\Property::where([
                    ['featured_property', '=', '1'],
                    ['supplier_id', '=', 123],
                ])
                ->orderBy('prop_id', 'desc')
                ->limit(6)
                ->get();

            View::share('featuredProperties', $featuredProperties);
        });
    }
}

Your view will not need to change at all.

Answer:

Best way to replace string in query is to user sql replace function. When working with large data sets there is performance issue to replace strings with php. Laravel has handy way to extend the build in Builder so you don’t have to write raw queries. Here is an example how to do it.

<?php

namespace App\Providers;

use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        Builder::macro('replace', function ($column, $searchText, $replaceText)
        {
            return $this->addSelect(DB::raw("replace($column, '$searchText', '$replaceText') as $column"));
        });


 }
}

With this Macro registered on the query builder your query will look like this.

$featuredProperties = DB::table('properties')
            ->where([
                ['featured_property', '=', '1'],
                ['supplier_id', '=', 123],
            ])
            ->replace('prop_url', 'http://domain.com/', 'http://www.domain.com/')
            ->orderBy('prop_id', 'desc')
            ->limit(6)
            ->get();

Answer:

The get() method of Illuminate\Database\Query\Builder returns an instance of Illuminate\Support\Collection. This class offers many methods for manipulating a collection of data.

You can learn about it more in the documentation.