Tuesday, October 24, 2017

Apex November 2017 Municipal Elections

It's that time of year again for municipal elections and we have two items coming up on the ballot, a parks bond and three seats on the town council.

First, let's talk about the Parks Bond. If you're unfamiliar with it, the town has put together a great FAQ that should address a lot of your questions. I asked John Brown, the Parks director, why the growth itself isn't enough to fund the initiatives and he pointed out that as we grow, those additional tax revenues are typically needed to offset the cost of providing services to meet the demands of the larger community (public safety, streets, infrastructure, etc). He also pointed out that if the funds are there, the council can opt to not issue some of the debt or increase taxes to repay it. If passed, the bond will add additional greenways (connecting 17 more neighborhoods!), provide for a senior center, and build out Pleasant Park (which could be useful for tournaments, meaning soccer parents, etc may not have to travel as much). In my opinion, this will have a great impact on our quality of life and we should all vote Yes for this.

Second, there are three spots opening up for the town council, Nicole Dozier, Gene Schulze, and Denise Wilkie's seats. Ms. Dozier is the only one running for re-election. There are five candidates running for these three seats:


I went to the candidate forum last night to hear from the candidates. If you were unable to make it, they will be posting a video on the Chamber website within a few days. Overall, the candidates seemed very similar to each other with very little disagreements.

So who has my votes? Well, I will be voting NDG (play on the NBA signs around town, encouraging people to vote for the block of Nicole, Brett, and Audra). Ms. Dozier impressed me with her experience, advocacy, and her drive to get a senior center in place. Mr. Hooks impressed me with his town experience (being on the planning board and a lifelong resident of Apex), poise, and volunteering with kids. Mr. Wilson impressed me with an emphasis on economic development.

Finally, if you want to see what the ballot will actually look like so you're prepared, you can see it here. Short and sweet!

Voting is November 7th! You can find election information here, including how to get an absentee ballot, your polling location, and more.


Wednesday, October 11, 2017

Watch out for Drupal 8.4!

I was using a Drupal composer template and a few days ago, it upgraded from 8.3 to 8.4 automatically. I noticed and didn't really think much of it, so I applied the database updates and went on my way. Today, two days afterwards, I ran across this article which pointed out that PHP 7 is required for the underlying Symfony framework. Our site is running on Debian 8, which has PHP 5.6 and powers other PHP applications, so I wasn't looking to update the underlying OS and possibly break my other PHP things.

So I downloaded a db snap from 8.3 and then re-applied the 8.4 update and took another database snapshot. Then I diff'd the database snapshots. The biggest changes seemed to be in the cache tables and removing and adding some revision columns. So I reverse-engineered a backgrade SQL script. With that, I updated the composer.json file from this:
        "drupal/core": "~8.0",

to this:
        "drupal/core": "8.3.*",

Then I took a precautionary database backup and then did a composer update, which took care of the code backgrade. Then I ran my script (drush sqlc < backgrade.sql) and then I did a drush entity-updates to actually update the database schemas to match the backgraded code.

Now I just need to ignore Drupal telling me about 8.4 until I'm ready to fully embrace PHP 7. I feel like something bigger needed to get my attention to the update in system requirements when I ran the initial composer update.

Thursday, September 14, 2017

Ode to Sendgrid

I recently have been playing around with Sendgrid and it is pretty amazing. I have had some experience with sending email programmatically with AWS' SES product and it's nice, especially with its SNS integration, but Sendgrid's killer feature is its speed. I had about 6,000 emails to send, each one personalized with an individual URL and the person's first name. With AWS and a multi-threaded PHP program, I can send about 15 emails per second, which would have taken almost 7 minutes to process. With Sendgrid, I create a template in their system with template tags and then I create a massive data structure with up to 1,000 entries and then post it to the Sendgrid API. Rinse/repeat. I was able to send my 6,000 emails in 6 seconds! Sendgrid took care of the personalization on their end and I was seeing emails being delivered very soon after. Wow. Here's my script code:

I've only scratched the surface - there's lots more Sendgrid offers. One of the nice things is that you can get an activity feed and some nice stats after you send an email to see how people respond:

Activity Log, showing opens, clicks, delivered, etc.

Stat report, showing email performance.



If you're a Google Cloud customer, they offer a free trial with up to 12,000 transactional emails per month. Definitely worth a checking out!

Monday, June 5, 2017

Global Climate

Climate change has been an ongoing issue for about 20 years (maybe more). I was influenced early on by Michael Crichton's book, State of Fear, which posits that there's no real scientific evidence of global warming. Plus, it was just another thing for humanity to focus on, that we need something to stay fearful of. It's an interesting theory, especially if you consider that the current growing fear of terrorism has probably reduced the fear of global warming, mitigating any resistance to progress. A few years ago, Casey shared this punnet-like square video of all the various perspectives of are we going to do anything about it and if global warming is caused by humans or not. So regardless of what you believe and if anything can be done, the risk of doing nothing seems a lot worse than the risk of doing something and it ending up not being a thing.

So with that out of the way, the current news of Trump and the Paris Climate Agreement has me more aware of local efforts. If the US government isn't going to lead the way, then we need to do what we can on a local level. As a North Carolina citizen, we have an opportunity to keep our state moving forward towards more renewable energy sources. There's a bill that was just filed with the NC House that rolls back some of the goals (the minimum stays at 6% vs. growing to 12.5% by 2021). I'm not a lawyer, but it also looks like nuclear energy will be considered renewable, which probably means that Duke Energy could just buy energy from its own nuclear plants vs. other renewable energy sources.

So, if you're a NC resident and you want to do something, you can find your representative and express your opinion. You can also look at what your town is up to.

Thursday, January 12, 2017

Drupal 8 project killed

I realize I haven't updated my blog in awhile, because I've been in the thick of developing and rolling out our new site (check it out!). This was taking our existing Drupal 7 site for FSR Magazine and migrating it to this new site, which is responsive and better catered to deal with all food service news. Unfortunately, we ended up killing the Drupal 8 upgrade because it was just too complex, given our custom module codebase. D8 has a mindset change, where everything is an Entity and it takes a lot of OOP and YAML files to get things done.

I figure it would cost us hundreds of hours to upgrade to D8 and it was tempting to upgrade to D8 for the configuration management, Big Pipe, and better caching features. But that's time and money we didn't want to spend on the upgrade.

I've talked with some folks at my local Drupal User Group (shoutout TriDUG!) and several others are in the same boat, where existing sites aren't upgrading, but rather, they're doing new sites in D8.

At some point, we'll need to do something, but we're able to do what we need with D7. If pressed, it may be easier to port to Backdrop vs. upgrade to D8.

Monday, June 20, 2016

Entities (Porting Drupal 7 modules to Drupal 8)

Preface

Wow. Entities. What a mess. I really struggled with this. There's an example of a database-based module not using them and one that does use them. I think it boiled down to me that what I was representing were reports, which seemed very similar to nodes (in hindsight, perhaps this should have been a content type all along), so I should use entities.

Entities

As I look through examples, trying to figure out how to get my menu routing to work with my database entries, it appears that I need to set them up as entities. I say that, looking at the xmlsitemap and dbtng_example and content_entity_example from the examples module. So, let me back up and learn about D8 Entities. It looks like entities were introduced in Drupal 7 and there is a guide for that, but it's based on D7, so the concepts stuff is ok, but the implementation guidance is outdated. There's a link at the top to take you to a D8-specific guide. The guide says it's just a holder, but I'm going to give it a go. If nothing else, perhaps this post will help others.

First, entity types. Either you're setting up a configuration entity or a content entity. I think content entity fits for me. Next, looking at requirements section of the content type doc, I need to setup src/Entity/GatedContentEntity.php file with docblock stuff that will give the loader the information it needs. Hey, checkout this helpful hint about double quotes in the annotations doc (post #1 reference)! That annotations doc doesn't provide what all you need, but I'll take those 3 lines to start off with. I think this doc may list all of the properties you can use with ContentEntityType, but not sure which ones I really need and how to use them. This is when the examples/content_entity_example/src/Entity/Contact.php file helped. I first just used the core form & access handlers for now.

Random Aside: Sublime Text 3

I use Sublime Text 3 as my IDE and there is a PHPDoc plugin that makes life editing these annotations easier. Package Manager makes it easy to install plugins: just follow the installation instructions (and restart ST), then follow the usage instructions to pull up the Install Package option and then type phpdoc and select it to install. Then restart ST again and you'll be much happier. I think the same applies for Sublime Text 2, too. This Drupal doc has some other helpful Sublime Text tips.

Back to it...

So I found this great doc on how to create your own content entity. And whoa ... this comment pointed me to the Drupal Console project, which can jumpstart this a LOT. So I tucked my handcrafted code away and ran this command:
drupal --target="fnmd8.local" gect --module="gated_content" --entity-class="GatedContent" --entity-name="gated_content" --label="Gated Content"
The end result is about 10 files with lots of boilerplate fields that don't match up with what I'm using. Maybe I'm going about this all wrong. Maybe I don't need Entities? Time to take a break...

After the weekend

So I went back and added the preface above and I feel I need to stick the course with Entities. Perhaps this post will be totally rewritten after I totally understand them and and write a better guide that flows better. I need to just release this post because I'm about to flip back to some D7 work and will be taking another break. I'll pick this post thread back up again shortly.

Thursday, June 16, 2016

Porting custom module from Drupal 7 to Drupal 8 (databases, block view, URLs, and some menu work)

This is the next steps in getting my module working in Drupal 8. In the previous post, I got the block setup working, but it's just a generic "Hello World!" output. The block gets the most recent entries from a database table, so my next step is getting the database setup.

Databases

This doesn't seem to have changed from Drupal 7 to 8. You basically copy over the .install file. The hook_schema() takes care of it. BTW, one thing that is different is if you need to uninstall/reinstall the modules, you just need to drush pm-uninstall MODULE vs. drush dis MODULE ; drush pm-uninstall MODULE.

Once I had the database tables in place, I dumped my database tables from D7 and imported them into D8. NOTE: I probably need to either codify this (perhaps there's a way to hook into the migration system?) or keep notes about what tables to dump/import once everything's in place.

Now I needed to update my block to query the most recent entries in the table. db_query() is still supported in D8, however it looks like it's going away for D9, so if I wanted to future-proof my module even more, I could convert it to the new injection technique. So I created a storage class in my src folder. I downloaded the examples module and looked at the dbtng_example submodule and its DbtngExampleStorage class. I also found this answer on StackOverflow and the example posted in the select doc helpful. I ended up with a hybrid approach:

So a few things there. First, my post_date field was a legacy datetime field, so I had to convert the current time from a timestamp to the date format that mysql expected. format_date() is deprecated, so they recommend using Drupal::service('date.formatter'). Also, I make my __construct() method a little dynamic so that I don't have to pass in a connection, but it can support it if it is passed in.

Then my block build() method uses my storage class and calls the get_reports() method, passing in the block's configuration array. First, I add use Drupal\gated_content\GCStorage; to the top of my block plugin. Then my build() looks like this:
  public function build() {
    $storage = new GCStorage;
    $reports = $storage->get_reports($this->configuration);
    $build = array();
    kint($reports, $build);
    return $build;
  }

Block View

So now that my block has the right set of reports to show, I need to show them. This takes me to the whole theming and templates stuff. The D7 version called theme() directly and there was a hook_theme and I had a template. The end result was a simple
and foreach report, a
and l(); call to link to the report. Kind of reminds me of item_list a bit, so I think I'll start there.

Ok, it looks like item_list looks about the same. My render array just needs to '#theme' => 'item_list'. But l() is gone. This comment pointed me in the right direction. So now it looks like this:
    $items = array();
    foreach ($reports as $report) {
      $url = Url::fromRoute('');
      $items[] = \Drupal::l(t($report['title']), $url);
    }
    $build = array(
      'report_list' => array(
        '#theme' => 'item_list',
        '#items' => $items,
        '#attributes' => array(
          'class' => 'report-block',
        ),
      ),
    );
Success!

Of course, these all link to just the homepage, which isn't very helpful.

Menu

So now I'm looking down another rabbit hole. The URL to my report is reports/[ID#]. But I use this module on separate domains, so the reports part is configurable. So I need to get that configuration setting to get the first bit of the URL. Actually, I think I can skip that since we'll be doing something different in our migration plans, so I can skip that part of the module. [postnote: if I do end up needing this, it looks like this shows how]

Also, I had this module all setup with pathauto to get a nice SEO-friendly public URL. Ok, so I need to setup the menu routing so the user can get to /gated_content and /gated_content/[ID#] and /gated_content/[ID#]/download. And I need to setup pathauto again.

First, all of those URL's were setup in D7 with type of MENU_CALLBACK, so according to this doc, I need to set them up in a .routing.yml file. This wasn't something DMU setup for me, so I did this from scratch. This was a great doc that showed some examples going from D7 to D8.

My first question is that I was familiar with named placeholders and how the name would be used to call a NAME_load() function to put together the argument that gets passed into the callback. But these examples are using book, which uses node and entity, so I need something that will use my own data type.

Stuff for the next post!