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.
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.