For the project I am working on we want to have multiple SMTP configurations in the database, which can be chosen at runtime. It's very easy to update the mail config using the config() helper, but for some reason that did not change the SMTP settings used to actually send the mail. I did a lot of research and found some answers for older versions of Laravel that did not work with 5.4.

Eventually I was able to find how to accomplish this from this post on Laravel.io. It seems that the mailer instance is created with the app, so updating the config won't change the properties of it which have already been set. To do that you need to use the following code:

extract(config('mail'));

$transport = \Swift_SmtpTransport::newInstance($host, $port);
// set encryption
if (isset($encryption)) $transport->setEncryption($encryption);
// set username and password
if (isset($username))
{
    $transport->setUsername($username);
    $transport->setPassword($password);
}

// set new swift mailer
Mail::setSwiftMailer(new \Swift_Mailer($transport));
// set from name and address
if (is_array($from) && isset($from['address']))
{
    Mail::alwaysFrom($from['address'], $from['name']);
}

If you execute this after you have updated the config it will create a new instance of swift_mailer with the update mail config. Once that is done you can just send the mails and it will use the proper SMTP server.

Labels: coding, laravel
No comments

Googlebot wreaking havoc

Tuesday 30 May 2017

This morning I wake up to emails saying that our Mailgun account has been disabled due to high volumes of email and high volumes of bounces. The logs indicate that way more emails have been sent than we had visitors. Emails are only sent as the result of a user clicking a link on the site, so I have no idea how this is possible.

After a few hours of investigation, it turns out Googlebot was crawling our site and kept following links and buttons that send emails. To prevent this from happening again I took a couple different precautions:

  • Added rel="nofollow" to the links and buttons in question.
  • Changed any link that generates an email from a GET to a POST
  • Added meta robots tags to the pages in question with "nofollow"

Hopefully that will prevent this sort of thing from happening again.

Labels: coding
No comments

Domain Routing in Laravel

Monday 15 May 2017

For quite a while I have been struggling to get domain routing working in Laravel. Subdomain routing comes out of the box, and what I read said that adding domain routing should be fairly easy.

The first thing to do is get the full domain passed into the router. By default Laravel only takes what comes before the first ".", so to get the full domain passed in you need to add this to your Providers/RouteServiceProvider.php file:

Route::pattern('domain', '[a-z0-9.\-]+');
 parent::boot();

Now you can access the full domain in the routes file, and you can do so by adding a route group:

Route::group(['domain' => '{domain}'], function ($domain) {

I tried to get the routes to take in the domain as a parameter and create themselves dynamically, but that did not work. So I ended up creating the route group. My issue was that some domains just use the normal route, and others were to have their own custom routes. I spent a while trying to get that working before just adding the route group. Inside the route group I check the database to see if this domain uses the normal routes or gets a special route. 

The next issue was how to pass the variables from the routes to the controller, when they do not come from the URL. The special routes can be accessed via URL: http://www.maindomain.com/site/1. I wanted to be able to map via http://www.customdomain.com to that URL, but to do so I needed to pass the parameters from the URL into the controller when the parameters do not exist in the URL. That took some more figuring out, but it turns out you can do it like this:

$app = app();
$controller = $app->make('App\Http\Controllers\WhateverController');
return $controller->callAction('show', ['request' => $request, 'id' => $id]);

The controller expects a Request object, and to get that passed in you need to add it to the route explicity:

Route::get('/', function ($domain, \Illuminate\Http\Request $request) {

With that addition I am able to map the custom domain to a specific controller and pass in variables which are determined in the routes file.

The final code looks like this:

Route::group(['domain' => '{domain}'], function ($domain) {
    Route::get('/', function ($domain, \Illuminate\Http\Request $request) {
        $site = \App\Website::where('host', $domain)->first();
        if($site){
            $app = app();
            $controller = $app->make('App\Http\Controllers\DomainController');
            return $controller->callAction('show', ['request' => $request, 'id' => $site->store_id]);
        } else {
            $app = app();
            $controller = $app->make('App\Http\Controllers\HomeController');
            return $controller->callAction('index', []);
        }
    });
});

So for route "/" it checks to see if the domain exists in a table in the database, if so calls DomainController with a parameter from the DB and the request. If not it calls HomeController.

After a long time spent trying to figure this out it turns out to be a lot simpler than I thought it would be. However now I need to add specific routes to some domains but not others. I don't expect that will be too different than the method I am currently using.

Labels: coding, laravel
No comments

Domain Routing in Laravel

Monday 15 May 2017

For quite a while I have been struggling to get domain routing working in Laravel. Subdomain routing comes out of the box, and what I read said that adding domain routing should be fairly easy.

The first thing to do is get the full domain passed into the router. By default Laravel only takes what comes before the first ".", so to get the full domain passed in you need to add this to your Providers/RouteServiceProvider.php file:

Route::pattern('domain', '[a-z0-9.\-]+');
 parent::boot();

Now you can access the full domain in the routes file, and you can do so by adding a route group:

Route::group(['domain' => '{domain}'], function ($domain) {

I tried to get the routes to take in the domain as a parameter and create themselves dynamically, but that did not work. So I ended up creating the route group. My issue was that some domains just use the normal route, and others were to have their own custom routes. I spent a while trying to get that working before just adding the route group. Inside the route group I check the database to see if this domain uses the normal routes or gets a special route. 

The next issue was how to pass the variables from the routes to the controller, when they do not come from the URL. The special routes can be accessed via URL: http://www.maindomain.com/site/1. I wanted to be able to map via http://www.customdomain.com to that URL, but to do so I needed to pass the parameters from the URL into the controller when the parameters do not exist in the URL. That took some more figuring out, but it turns out you can do it like this:

$app = app();
$controller = $app->make('App\Http\Controllers\WhateverController');
return $controller->callAction('show', ['request' => $request, 'id' => $id]);

The controller expects a Request object, and to get that passed in you need to add it to the route explicity:

Route::get('/', function ($domain, \Illuminate\Http\Request $request) {

With that addition I am able to map the custom domain to a specific controller and pass in variables which are determined in the routes file.

The final code looks like this:

Route::group(['domain' => '{domain}'], function ($domain) {
    Route::get('/', function ($domain, \Illuminate\Http\Request $request) {
        $site = \App\Website::where('host', $domain)->first();
        if($site){
            $app = app();
            $controller = $app->make('App\Http\Controllers\DomainController');
            return $controller->callAction('show', ['request' => $request, 'id' => $site->store_id]);
        } else {
            $app = app();
            $controller = $app->make('App\Http\Controllers\HomeController');
            return $controller->callAction('index', []);
        }
    });
});

So for route "/" it checks to see if the domain exists in a table in the database, if so calls DomainController with a parameter from the DB and the request. If not it calls HomeController.

After a long time spent trying to figure this out it turns out to be a lot simpler than I thought it would be. However now I need to add specific routes to some domains but not others. I don't expect that will be too different than the method I am currently using.

Labels: coding, laravel
No comments

Queries by Key in Laravel

Sunday 07 May 2017

One of my greatest frustrations with Eloquent collections has been that I needed to loop through the collection and either create a new collection or array if I wanted to have the results in a format where I could access them by a value in the query results. That is to say if I want to be able to access the results by say primary key, I would need to loop through the Collection returned by the query and create a new object with the key as whatever I wanted it to be.

I just learned that there is a much easier way to do this: 

    Model::all()->keyBy('whatever');

This will return the collection with "whatever" as the key, which makes life so much easier and code so much cleaner.

Labels: coding, laravel
No comments