Eloquent | Setting column value on insert - laravel

So currently, I have a table which is for business locations in order to seperate the locations from the actual businesses table.
In this table, I want to store the longitude and latitude and obviously I can't get the user to input that without requiring them to do manual work which I really want to avoid.
So I wrote a class in order to get the longitude and latitude ready for entry to the database.
I've read online about doing setLongitudeAttribute() function within the model but I'm basing it off of the whole address which they are entering so I need to capture the whole of the request and then input it in myself.
I understand I can do this in the controller and do a custom insert but I didn't know if it was possible to keep it all contained within the model.
So essentially to break it down.
User inputs the full address including all address lines and postal/zip code.
Eloquent model captures this data.
Converts the address to long and lat
Model then handles the request in order to set the longitude and latitude based on the address.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Http\Calls\LongitudeLatitude;
class BusinessLocation extends Model
{
/**
* #var array
*/
protected $fillable = [
'business_id',
'address_line_1',
'address_line_2',
'address_line_3',
'address_line_4',
'postcode',
'longitude',
'latitude'
];
/**
* #var string
*/
protected $address;
protected function setAddressLine1Attribute($value)
{
$this->address .= $value . '+';
$this->attributes['address_line_1'] = $value;
}
protected function setAddressLine2Attribute($value)
{
$this->address .= $value . '+';
$this->attributes['address_line_2'] = $value;
}
protected function setAddressLine3Attribute($value)
{
$this->address .= $value . '+';
$this->attributes['address_line_3'] = $value;
}
protected function setAddress4Attribute($value)
{
$this->address .= $value . '+';
$this->attributes['address_line_4'] = $value;
}
protected function setPostcodeAttribute($value)
{
$this->address .= $value;
$this->attributes['postcode'] = $value;
$this->setCoordinates();
}
protected function setCoordinates()
{
$long_lat = new LongitudeLatitude();
$coords = $long_lat->get($this->address);
$this->attributes['longitude'] = $coords['longitude'];
$this->attributes['latitude'] = $coords['latitude'];
}

First of all, and as you correctly mention, it doesn't look a good practice to write this logic in the model, but in the controller.
If I were you, I would leave the model logic to the model, and the rest to the controller and the other objects required to "transform" the input. This could even be done from the client with some javascript library if you will, sending just the lat and long to the server (your choice).
If you still want to create a method / setter in the model, I would recommend you to get some inspiration in any consolidated library, and the more official, the best, as for example: Laravel Cashier (https://laravel.com/docs/5.6/billing), which provides extra behaviours to the model, in a separate logic unit.
If you want to follow Laravel Cashier approach, it uses a trait to apply the behaviour:
https://github.com/laravel/cashier/blob/7.0/src/Billable.php
It uses the official stripe package (it could be, in your case, the class you wrote, or any other such as Google Maps SDK) as well as other helper and data objects, which would make your model lighter in memory and make another objects responsible of the additional logic (easier to test and maintain), while it would also embed the behaviours in your model.
About your code...
You can not guarantee that the setters are executed in the order you want (or you shouldn't trust it will always do), so, in case you want to continue with the code you already have, I would suggest to move $this->setCoordinates(); to somewhere else, for example an event observer such as creating, updating and saving:
https://laravel.com/docs/5.6/eloquent#events
And also evaluate the address every time you call it, as follows:
public function setCoordinates()
{
$address = $this->attributes['address_line_1'] . '+' .
$this->attributes['address_line_2'] . '+' .
$this->attributes['address_line_3'] . '+' .
$this->attributes['address_line_4'] . '+' .
$this->attributes['postcode'];
$long_lat = new LongitudeLatitude();
$coords = $long_lat->get($address);
$this->attributes['longitude'] = $coords['longitude'];
$this->attributes['latitude'] = $coords['latitude'];
}
Notice that the method is public, so you can call it from the event observer.
There are other business logic factors to consider:
- Do you want to give the possibility to fill manually the coordinates? if not, you should remove them from the $fillable array.
- Do you really need to evaluate the coordinates every time you update any of the address fields? Then you may want to have a status property to verify if the coordinates are pristine (in address fields setters set pristine as false, in coordinates getters check if the value is null or if it's not pristine, and if so, set the coordinates again).

Related

How to overwrite bulk clone function for laravel backpack

I am trying to overwrite a bulk clone function.
I just want to clone some values, and the rest assign a static value. For example, I just want to clone the name and description values, and the date I want to assign it the current date.
And well I don't know how to do it.
use \Backpack\CRUD\app\Http\Controllers\Operations\BulkCloneOperation { bulkClone as traitBulkClone; }
public function bulkClone($id) {
// your custom code here
$this->traitBulkClone($id);
}
TLDR: The most efficient way would probably be to overwrite the replicate() method on your model. Note that it is not a Backpack method, but an Eloquent method that BulkCloneOperation uses to duplicate a particular entry.
WHY?
Inside the BulkCloneOperation that you're using, you'll notice the route calls the bulkClone() method, that itself is just making some calls to the replicate() method on the model. That means you have two options to override this behaviour:
(Option A). Override the bulkClone() method in your CrudController. This will override the behaviour only on that particular admin operation.
(Option B). Override the replicate() method in you Model. That way, any time replicate() is called (by your admin panel or any other part of your software), the duplication is done in the way you specified.
In most cases, I think Option B is more appropriate, since it would avoid future code duplication. Here's Laravel's replicate() method at this time, just copy-pasting it into your model and modifying it to fit your needs is the best solution, if you ask me:
/**
* Clone the model into a new, non-existing instance.
*
* #param array|null $except
* #return static
*/
public function replicate(array $except = null)
{
$defaults = [
$this->getKeyName(),
$this->getCreatedAtColumn(),
$this->getUpdatedAtColumn(),
];
$attributes = Arr::except(
$this->getAttributes(), $except ? array_unique(array_merge($except, $defaults)) : $defaults
);
return tap(new static, function ($instance) use ($attributes) {
$instance->setRawAttributes($attributes);
$instance->setRelations($this->relations);
$instance->fireModelEvent('replicating', false);
});
}

Enabling / Disabling Features in a Laravel App

I'm building a Laravel app, which has a number of various features. I want to be able to enable or disable them depending on a particular domain's requirement. Currently, I have in my config a series of flags such as:
'is_feature_1_enabled' => true,
'is_feature_2_enabled' => false,
... and so on.
Then in my controllers and views, I check those config values to see whether or not I should be displaying something, allowing certain actions, etc. My app is starting to get polluted with these kinds of checks everywhere.
Is there a best practice method of managing features in a Laravel app?
This is technically called feature flags - https://martinfowler.com/articles/feature-toggles.html
depends on your requirements, flags in config/database, rollout, etc...
But it's basically if's in code and cannot be clean.
Laravel packages:
https://github.com/alfred-nutile-inc/laravel-feature-flag
https://github.com/francescomalatesta/laravel-feature
Some services:
https://launchdarkly.com/
https://bullet-train.io/
https://configcat.com/
Also look at https://marketingplatform.google.com/about/optimize/ for frontend.
I've encountered the same problem when I tried to implement multiple hotel providers.
What I did was using service container.
first you will create class for each domain With his features:
like Doman1.php ,Domain2.php
then inside each one of those you will add your logic.
then you gonna use binding in your app service provider to bind the domain with class to use.
$this->app->bind('Domain1',function (){
return new Domain1();
});
$this->app->bind('Domain2',function (){
return new Domain2();
});
Note you can use general class that holds the features goes with all domains then use that general class in your classes
Finally in your controller you can check your domain then to use the class you gonna use
app(url('/'))->methodName();
Look like you are hard coding things based on config values to enable or disable certain features. I recommend you to control things based on named routes rather than config value.
Group all the route as a whole or by feature wise.
Define name for all routes
Control the enable/disable activity by route name and record in database
Use Laravel middleware to check whether a particular feature is enabled or disabled by getting the current route name from request object and matching it with the database..
so you will not have the same conditions repeating every where and bloat your code..
here is a sample code show you how to retrieve all routes, and you can match the route group name to further process to match your situation.
Route::get('routes', function() {
$routeCollection = Route::getRoutes();
echo "<table >";
echo "<tr>";
echo "<td width='10%'><h4>HTTP Method</h4></td>";
echo "<td width='10%'><h4>Route</h4></td>";
echo "<td width='80%'><h4>Corresponding Action</h4></td>";
echo "</tr>";
foreach ($routeCollection as $value) {
echo "<tr>";
echo "<td>" . $value->getMethods()[0] . "</td>";
echo "<td>" . $value->getPath() . "</td>";
echo "<td>" . $value->getName() . "</td>";
echo "</tr>";
}
echo "</table>";
});
and here is a sample middleware handler where you can check whether a particular feature is active by matching with what you have already stored in your database..
public function handle($request, Closure $next)
{
if(Helper::isDisabled($request->route()->getName())){
abort(403,'This feature is disabled.');
}
return $next($request);
}
Assuming that those features are only needed for HTTP requests.
I would create a default Features base class with all the default flags:
Class Features {
// Defaults
protected $feature1_enabled = true;
protected $feature2_enabled = true;
public function isFeature1Enabled(): bool
{
return $this->feature1_enabled;
}
public function isFeature2Enabled(): bool
{
return $this->feature2_enabled;
}
}
Then I would extend that class for each Domain and set the overrides that are needed for that domain:
Class Domain1 extends Features {
// override
protected $feature1_enabled = false;
}
Then create a Middleware to bind the Features Class to the container:
class AssignFeatureByDomain
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
switch ($_SERVER['HTTP_HOST']) {
case 'domain1':
app()->bind(Features::class, Domain1::class);
break;
default:
abort(401, 'Domain rejected');
}
return $next($request);
}
}
Don't forget to attach this middleware to your routes: to a group or for each route.
After this you can TypeHint your Features class in your controllers:
public function index(Request $request, Features $features)
{
if ($features->isFeature1Enabled()) {
//
}
}
Laravel is great with this, you can even store your features in db, and create a relation between the domain.
I would recommend to use Gates and Policies, which will give you better control in your controllers and blade templates. This means you register the gates from your db or hard code them.
For example if you have export products feature with a button in your system and you want to make that feature available to some users you can register gates with business logic.
//Only admins can export products
Gate::define('export-products', function ($user) {
return $user->isAdmin;
});
Then you can do the following in the controllers
<?php
namespace App\Http\Controllers;
use App\Product;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class ProductsController extends Controller
{
/**
* Export products
*
* #param Request $request
* #param Post $post
* #return Response
* #throws \Illuminate\Auth\Access\AuthorizationException
*/
public function export(Request $request)
{
$this->authorize('export-products');
// The current user can export products
}
}
Here is an example for your blade templates:
#can('export-products', $post)
<!-- The Current User Can export products -->
#endcan
#cannot('export-products')
<!-- The Current User Can't export products -->
#endcannot
more information available at https://laravel.com/docs/5.8/authorization
Interesting case you have here. It might be interesting to look into a Feature interface or abstract class that contains a few methods you generally need.
interface Feature
{
public function isEnabled(): bool;
public function render(): string;
// Not entirely sure if this would be a best practice but the idea is to be
// able to call $feature->execute(...) on any feature.
public function execute(...);
...
}
You could even devide these into ExecutableFeature and RenderableFeature.
Further on some kind of factory class could be made to make life easier.
// Call class factory.
Feature::make('some_feature')->render();
...->isEnabled();
// Make helper method.
feature('some_feature')->render();
// Make a blade directives.
#feature('some_feature')
#featureEnabled('some_feature')
What I did in my case was creating a new table on the database, you could call it Domains for instance.
Add all the specific features, those which could be shown on some domains but not in the rest, as columns for that table as bit for boolean values. Like, in my case, allow_multiple_bookings, use_company_card... whatever.
Then, consider creating a class Domain and its respective repository, and just ask these values on your code, trying to push as much as possible that logic into your domain (your model, application services, etc).
For instance, I would not do the check on the controller method for RequestBooking if the domain which is requesting a booking can request only one or more.
Instead I do it on a RequestBookingValidatorService which can check if the booking datetime has passed, the user has an enabled credit card, ... or the Domain which this action comes from is allowed to request more than one booking (and then if it already has any).
This adds the convenience of readability, as you have pushed this decision to your application services. Also, I find that whenever I need a new feature I can use Laravel (or Symfony) migrations to add that feature on the table and I could even update its rows (your domains) with the values I want on the same commit I coded.

How can I change SMTP details globally at runtime?

I'm using Laravel 5.5. The nature of the website is a 'multisite' architecture where multiple websites/domains are run from the same codebase.
I've come across an issue when sending email. I need to change the from name and address as well as the transport (SMTP, etc) options depending on which website is being viewed. I have these details stored in a config file.
The easiest way is to just pull those details in the Controller before I call Mail::send/Mail::queue and to update them. However, this brings back 2 issues:
There is a heavy reliance on remembering to actually do that every time I send any email in the code. In short, it's not abiding by DRY.
I'd be forced to use Mail::send instead of Mail::queue, because the queue wouldn't have any idea of the config update from the time it was queued only from when it is processed .
How can I achieve what I am looking to do here in a clean way?
I thought about extending all of my 'Mailable' classes with a custom class that updates the SMTP details, but it doesn't look like you can update the SMTP/Transport information after the class is initiated; you can only update the from name and address.
I managed to find a way to do this.
I had my mailable class (ContactFormMailable) extend a custom class, as follows:
<?php
namespace CustomGlobal\Mail;
use CustomGlobal\Mail\CustomMailable;
use CustomGlobal\ContactForm;
class ContactFormMailable extends CustomMailable
{
public $contact_form;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct(ContactForm $contact_form)
{
$this->contact_form = $contact_form;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
$view = $this->get_custom_mail_view('contact_form', $this->contact_form);
return $this->subject('Contact Form Enquiry')
->view($view);
}
}
You'll notice I'm calling get_custom_mail_view. This is in my extended class and used to calculate the view and template I need to use for my mail, depending on the website being viewed. In here I also set the location of my config folder.
<?php
namespace CustomGlobal\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
use Swift_Mailer;
use Swift_SmtpTransport;
use CustomGlobal\Website;
use CustomGlobal\Territory;
class CustomMailable extends Mailable
{
use Queueable, SerializesModels;
public $layout_view_to_serve;
public $host_folder;
/**
* Override Mailable functionality to support per-user mail settings
*
* #param \Illuminate\Contracts\Mail\Mailer $mailer
* #return void
*/
public function send(Mailer $mailer)
{
app()->call([$this, 'build']);
$config = config($this->host_folder .'.mail');
// Set SMTP details for this host
$host = $config['host'];
$port = $config['port'];
$encryption = $config['encryption'];
$transport = new Swift_SmtpTransport( $host, $port, $encryption );
$transport->setUsername($config['username']);
$transport->setPassword($config['password']);
$mailer->setSwiftMailer(new Swift_Mailer($transport));
$mailer->send($this->buildView(), $this->buildViewData(), function ($message) use($config) {
$message->from([$config['from']['address'] => $config['from']['name']]);
$this->buildFrom($message)
->buildRecipients($message)
->buildSubject($message)
->buildAttachments($message)
->runCallbacks($message);
});
}
/**
* Calculate the template we need to serve.
* $entity can be any object but it must contain a
* $website_id and $territory_id, as that is used
* to calculate the path.
*/
public function get_custom_mail_view($view_filename, $entity)
{
if(empty($view_filename)) {
throw new Exception('The get_custom_mail_view method requires a view to be passed as parameter 1.');
}
if(empty($entity->website_id) || empty($entity->territory_id)) {
throw new Exception('The get_custom_mail_view method must be passed an object containing a website_id and territory_id value.');
}
// Get the website and territory
$website = Website::findOrFail($entity->website_id);
$territory = Territory::findOrFail($entity->territory_id);
$view_to_serve = false;
$layout_view_to_serve = false;
// Be sure to replace . with _, as Laravel doesn't play nice with dots in folder names
$host_folder = str_replace('.', '_', $website->website_domain);
$this->host_folder = $host_folder; // Used for mail config later
/***
Truncated for readability. What's in this area isn't really important to this answer.
***/
$this->layout_view_to_serve = $layout_view_to_serve;
return $view_to_serve;
}
}
It's important to remember that mail can be queued. If you do this is another way, such as setting a config at runtime, then you'll find that the process that runs the queue has no visibility/scope of your runtime config changes, and you'll end up firing out email from your default values.
I found a few answers similar to this one, which helped me out, but none of them worked completely, and some are out-dated (Swift_SmtpTransport is changed considerably since those answers).
Hopefully this helps someone else out.

Where to manipulate data after controller grabs it from entity in zf2

In zf2, I'm writing a viewAction that grabs a record from a doctrine entity and passes the data to a view. After I've grabbed the data and before I pass it to the view, there's a bunch of conversion, formatting, concatenation, parsing and other manipulation I want to do to it. For example, the script below develops different $eventDate and $eventTime strings depending upon whether an event is a single- or multi-day event:
if ($eventStartDate==$eventEndDate) {
$eventStartDate = $eventStartDate->format('n/j/y');
$eventEndDate = $eventEndDate->format('n/j/y');
$eventDate = $eventStartDate;
$eventStartTime = $eventStartTime->format('g:i a');
$eventEndTime = $eventEndTime->format('g:i a');
$eventTime = "<strong>time: </strong>" . $eventStartTime . " - " . $eventEndTime . "<br/>";
} else {
$eventStartDate = $eventStartDate->format('n/j');
$eventEndDate = $eventEndDate->format('n/j/y');
$eventDate = $eventStartDate . " - " . $eventEndDate;
$eventStartTime = $eventStartTime->format('g:i a');
$eventEndTime = $eventEndTime->format('g:i a');
$eventTime = "<strong>departure: </strong>" . $eventStartTime . "<br/>";
$eventTime .= "<strong>return: </strong>" . $eventEndTime . "<br/>";
}
This is just one of a collection of similar scripts that I've got written in my controller. However, in an MVC pattern, it doesn't seem like they belong there. My question is, where should this and similar scripts be written?
You definitely need a Service Layer to apply such business logic, process your entity, generate a result and return that result back to the caller rather than handling all of this logic in controller level.
ZF's built-in Service Manager would be good place to take a look.
I would handle that requirement by following steps:
Write an EventService class under Application\Service namespace. This EventService depends on doctrine's EventRepository and/or ObjectManager's itself. Pass this dependencies to the EventService on construction time (you'll need a factory) or take a look ObjectManagerAwareInterface to figure out other ways (ie: setter injection).
This EventService would have some public methods like getEventById($id), getEventsByDateInterval(\DateTime $from, \DateTime $to), getActiveEvents() etc..
These public methods like getEventById($id) are interfaces for your controllers to interact your application.
EventService should/may have some private methods which doesn't accessible from outside and apply some domain rules on resultset or manipulate the business objects or even generates some other lightweight data transfer objects for specific use cases, internally.
Write a FooEvent entity. This entity would have some properties like $id, $title, $startDate, $endDate and these entity properties probably will be persisted to database.
In FooEvent entity, write a one-line public method named isSingleDayEvent() which returns a boolean.
Something like:
public function isSingleDayEvent()
{
return $this->getStartDate() === $this->getEndDate();
}
After all of this fancy steps, our viewAction can be written something like this:
public function viewAction()
{
$id = (int) $this->params()->fromQuery('id');
// Event instance can be an actual entity or DTO
$event = $this->eventService->getEventById($id);
$viewModel = new ViewModel();
$viewModel->setVariable('event', $event);
return $viewModel;
}
Now, we can write a ViewHelper with a name like EventRenderer to easily render events on view layer. The key point is our view helper accepts an \FooEvent (or DTO) instance to work. (This is not a MUST too)
For example:
<?php
namespace Application\View\Helper;
use Zend\View\Helper\AbstractHelper;
use Application\Entity\FooEvent;
class EventRenderer extends AbstractHelper
{
public function __invoke(FooEvent $event)
{
$time = '';
if($event->isSingleDayEvent()) {
$time = '<strong>Time: </strong>: '. $event->getStartTime();
} else {
$time = '<strong>Time: </strong>: '. $event->getStartTime();
}
return $time;
}
}
I simplified the helper for the sake of other key points.
And finally, inside the view.phtml, we can output the result of all this complex processes writing fewer lines:
echo '<h1>Event: '.$this->event->getTitle().'</h1>';
echo $this->eventRenderer($this->event);
By this way, in the next stages of your project when you need a REST service for example, writing and API endpoint will be easy as pie: writing a RestfulEventController will be enough while re-using whole service layer, without duplicating bunch of code.
I tried to summarize some best practices from my perspective, some steps definitely can be improved. I write this detailed answer because I asked similar questions several times in the past and couldn't find the exact answer but the partial solutions.

Translate controller class variables in zend framework 2

Let's say I have a controller and I want to define some const variables that hold some messages (eg error messages etc).
Is there a way to make it so they are translated?
An example class is defined bellow:
<?php
namespace Test\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class AccountController extends AbstractActionController
{
protected $testError = 'There was an error while testing some stuff';
public function testAction(){
// I know i can use the following approach but I don't want to
// since I want to use a variable for readability issues.
// $testError = $this->getTranslator()->translate('There was an error..');
return new ViewModel();
}
/**
* Retrieve the translator
*
* #return \Zend\I18n\Translator\Translator
*/
public function getTranslator()
{
if (!$this->translator) {
$this->setTranslator($this->getServiceLocator()->get('translator'));
}
return $this->translator;
}
/**
* Set the translator
*
* #param $translator
*/
public function setTranslator($translator)
{
$this->translator = $translator;
}
}
So I want to have the testError translated. I know I can just use the message and translate it via the zend translator without using a variable, but still I want to store it in a variable for readability issues. Any help or other approaches to this?
Simply create a translations.phtml file in any directory in your project root and fill it something like that:
<?php
// Colors
_('Black');
_('White');
_('Green');
_('Light Green');
_('Blue');
_('Orange');
_('Red');
_('Pink');
In poedit, check Catalog Properties > Source keywords list an be sure _ character is exists. (Alias of the gettext method). In application, use $this->translate($colorName) for example.
When poedit scanning your project directory to find the keywords which needs to be translated, translations.phtml file will be scanned too.
Another handy approach is using _ method (gettext alias) to improve code readability. Example:
$this->errorMsg = _('There was an error..');
But don't forget to set the global Locale object's default locale value too when you initialising your translator instance first time in a TranslatorServiceFactory or onBootstrap method of the module:
...
$translator = \Zend\Mvc\I18n\Translator\Translator::factory($config['translator']);
$locale = 'en_US';
$translator->setLocale($locale);
\Locale::setDefault($translator->getLocale());
return $translator;
...
I don't quite understand what you mean:
$errorMessage = 'FooBarBazBat";
return new ViewModel(array(
'error' => $this->getTranslator()->translate($errorMessage)
));
would be a way to store the message inside a variable. But i really don't understand where your problem is.
Or do you mean having the translator as variable?
$translator = $this->getServiceLocator()->get('viewhelpermanager')->get('translate');
$errorMessage = $translator('FooBarBazBat');

Resources