emGee Software Solutions Custom Database Applications

Share this

Planet PHP

People blogging about PHP
Updated: 3 days 5 hours ago

3 weeks without coffee - Stefan Koopmanschap

Fri, 03/16/2018 - 08:50

Three weeks ago I decided that was going to take a break from coffee. Every once in a while I take a break from certain things, or try to minimize their usage. Some months ago I minimized the amount of soft drinks I was drinking, and 3 weeks ago it was time to quit coffee. I wanted to break the habit and get rid of my caffeine dependence.

I'd done this before so I knew what to expect, and it was not very different this time around. The first day was fine except for the habit of getting coffee, and I accidentally got coffee when visiting a client, out of habit. "You want coffee?" I got asked, and just like that I said "sure". I didn't realize I had quit coffee until I already finished half of it. The second and third day I had some headaches and got a lot of urges to get coffee. I resisted the urges, got water or tea instead, and pretty much got off my addiction (or dependence, or habit). From day 4 onward I had pretty much no urge to get coffee anymore.

The effects? I sleep better. I wake up less tired (well, except when I go to bed really late of course). I am also less tense, feel more relaxed. Long story short, it just feels a bit better.

I intend to keep this up for a long time. Now that I'm used to not drinking coffee, it's not that hard anymore, and the urge to get coffee is gone. I'm also considering doing a similar habit-breaking experiment with alcohol.

If you've got similar experiences with experiments like these, I'd be happy to hear from you.

Categories: Web Technologies

Defining multiple similar services with Docker Compose - Matthias Noback

Tue, 03/13/2018 - 01:09

For my new workshop - "Building Autonomous Services" - I needed to define several Docker containers/services with more or less the same setup:

  1. A PHP-FPM process for running the service's PHP code.
  2. An Nginx process for serving static and dynamic requests (using the PHP-FPM process as backend).

To route requests properly, every Nginx service would have its own hostvcname. I didn't want to do complicated things with ports though - the Nginx services should all listen to port 80. However, on the host machine, only one service can listen on port 80. This is where reverse HTTP proxy Traefik did a good job: it is the only service listening on the host on port 80, and it forwards requests to the right service based on the host name from the request.

This is the configuration I came up with, but this is only for the "purchase" service. Eventually I'd need this configuration about 4 times.

services: purchase_web: image: matthiasnoback/building_autonomous_services_purchase_web restart: on-failure networks: - traefik - default labels: - "traefik.enable=true" - "traefik.docker.network=traefik" - "traefik.port=80" volumes: - ./:/opt:cached depends_on: - purchase_php labels: - "traefik.backend=purchase_web" - "traefik.frontend.rule=Host:purchase.localhost" purchase_php_fpm: image: matthiasnoback/building_autonomous_services_php_fpm restart: on-failure env_file: .env user: ${HOST_UID}:${HOST_GID} networks: - traefik - default environment: XDEBUG_CONFIG: "remote_host=${DOCKER_HOST_NAME_OR_IP}" volumes: - ./:/opt:cached Using Docker Compose's extend functionality

Even though I usually favor composition over inheritance, also for configuration, in this case I thought I'd be better of with inheriting some configuration instead of copying it. These services don't accidentally share some setting, in the context of this workshop, these services are meant to be more or less identical, except for some variables, like the host name.

So I decided to define a "template" for each service in docker/templates.yml:

version: '2' services: web: restart: on-failure networks: - traefik - default labels: - "traefik.enable=true" - "traefik.docker.network=traefik" - "traefik.port=80" volumes: - ${PWD}:/opt:cached php-fpm: image: matthiasnoback/building_autonomous_services_php_fpm restart: on-failure env_file: .env user: ${HOST_UID}:${HOST_GID} networks: - traefik - default environment: XDEBUG_CONFIG: "remote_host=${DOCKER_HOST_NAME_OR_IP}" volumes: - ${PWD}:/opt:cached

Then in docker-compose.yml you can fill in the details of these templates by using the extends key (please note that you'd have to use "version 2" for that):

services: purchase_web: image: matthiasnoback/building_autonomous_services_purchase_web extends: file: docker/templates.yml service: web depends_on: - purchase_php labels: - "traefik.backend=purchase_web" - "traefik.frontend.rule=Host:purchase.localhost" purchase_php_fpm: extends: file: docker/templates.yml service: php-fpm

We only define the things that can't be inherited (like depends_on), or that are specific to the actual service (host name).

Dynamically generate Nginx configuration

Finally, I was looking for a way to get rid of specific Nginx images for every one of those "web" services. I started with a Dockerfile for every one of them, and a specific Nginx configuration file for each:

server { listen 80 default_server; index index.php; server_name purchase.localhost; root /opt/src/Purchase/public; location / { # try to serve file directly, fallback to index.php try_files $uri /index.php$is_args$args; } location ~ ^/index\.php(/|$) { fastcgi_pass purchase_php_fpm:9000; fastcgi_split_path_info ^(.+\.php)(/.*)$; include fastcgi_params;

Truncated by Planet PHP, read more at the original (another 3311 bytes)

Categories: Web Technologies

Automated Dependency Injection using Containers - Andrew Embler

Fri, 03/09/2018 - 04:10

Containers can build your PHP objects for you, saving you time and trouble.
Categories: Web Technologies

Debugging a Nextcloud error - Christian Weiske

Wed, 03/07/2018 - 07:54

A couple of days ago I had a problem with the Nextcloud instance on my server: A certain user could not access his files; the web interface only gave the error message Operation not permitted in a popup.

This blog post describes how I debugged and solved the issue.

Error log message decryption

I found a log entry in /var/lib/owncloud/wolke/data/nextcloud.log that looked like this:

{"reqId":"hbknW8t3PU72y4L3Cm09","level":4,"time":"2018-03-02T19:15:51+00:00","remoteAddr":"","user":"alice@example.org","app":"webdav","method":"PROPFIND","url":"\/remote.php\/webdav\/","message":"Exception: {\"Exception\":\"OCA\\\\DAV\\\\Connector\\\\Sabre\\\\Exception\\\\Forbidden\",\"Message\":\"No read permissions\",\"Code\":0,\"Trace\":\"#0 \\\/var\\\/www\\\/system\\\/wolke.cweiske.de\\\/apps\\\/dav\\\/lib\\\/Connector\\\/Sabre\\\/TagsPlugin.php(222): OCA\\\\DAV\\\\Connector\\\\Sabre\\\\Directory->getChildren()\\n#1 [internal function]: OCA\\\\DAV\\\\Connector\\\\Sabre\\\\TagsPlugin->handleGetProperties(Object(Sabre\\\\DAV\\\\PropFind), Object(OCA\\\\DAV\\\\Connector\\\\Sabre\\\\Directory))\\n#2 \\\/var\\\/www\\\/system\\\/wolke.cweiske.de\\\/3rdparty\\\/sabre\\\/event\\\/lib\\\/EventEmitterTrait.php(105): call_user_func_array(Array, Array)\\n#3 \\\/var\\\/www\\\/system\\\/wolke.cweiske.de\\\/3rdparty\\\/sabre\\\/dav\\\/lib\\\/DAV\\\/Server.php(1059): Sabre\\\\Event\\\\EventEmitter->emit('propFind', Array)\\n#4 \\\/var\\\/www\\\/system\\\/wolke.cweiske.de\\\/3rdparty\\\/sabre\\\/dav\\\/lib\\\/DAV\\\/Server.php(981): Sabre\\\\DAV\\\\Server->getPropertiesByNode(Object(Sabre\\\\DAV\\\\PropFind), Object(OCA\\\\DAV\\\\Connector\\\\Sabre\\\\Directory))\\n#5 \\\/var\\\/www\\\/system\\\/wolke.cweiske.de\\\/3rdparty\\\/sabre\\\/dav\\\/lib\\\/DAV\\\/Server.php(1666): Sabre\\\\DAV\\\\Server->getPropertiesIteratorForPath('', Array, 1)\\n#6 \\\/var\\\/www\\\/system\\\/wolke.cweiske.de\\\/3rdparty\\\/sabre\\\/dav\\\/lib\\\/DAV\\\/CorePlugin.php(355): Sabre\\\\DAV\\\\Server->generateMultiStatus(Object(Generator), false)\\n#7 [internal function]: Sabre\\\\DAV\\\\CorePlugin->httpPropFind(Object(Sabre\\\\HTTP\\\\Request), Object(Sabre\\\\HTTP\\\\Response))\\n#8 \\\/var\\\/www\\\/system\\\/wolke.cweiske.de\\\/3rdparty\\\/sabre\\\/event\\\/lib\\\/EventEmitterTrait.php(105): call_user_func_array(Array, Array)\\n#9 \\\/var\\\/www\\\/system\\\/wolke.cweiske.de\\\/3rdparty\\\/sabre\\\/dav\\\/lib\\\/DAV\\\/Server.php(479): Sabre\\\\Event\\\\EventEmitter->emit('method:PROPFIND', Array)\\n#10 \\\/var\\\/www\\\/system\\\/wolke.cweiske.de\\\/3rdparty\\\/sabre\\\/dav\\\/lib\\\/DAV\\\/Server.php(254): Sabre\\\\DAV\\\\Server->invokeMethod(Object(Sabre\\\\HTTP\\\\Request), Object(Sabre\\\\HTTP\\\\Response))\\n#11 \\\/var\\\/www\\\/system\\\/wolke.cweiske.de\\\/apps\\\/dav\\\/appinfo\\\/v1\\\/webdav.php(76): Sabre\\\\DAV\\\\Server->exec()\\n#12 \\\/var\\\/www\\\/system\\\/wolke.cweiske.de\\\/remote.php(162): require_once('\\\/var\\\/www\\\/system...')\\n#13 {main}\",\"File\":\"\\\/var\\\/www\\\/system\\\/wolke.cweiske.de\\\/apps\\\/dav\\\/lib\\\/Connector\\\/Sabre\\\/Directory.php\",\"Line\":254}","userAgent":"Mozilla\/5.0 (X11; Ubuntu; Linux x86_64; rv:58.0) Gecko\/20100101 Firefox\/58.0","version":""}

But strings beginning with { are usually JSON encoded, so I put the line into a file error.json and let jq format it:

$ jq . getChildren()\\n#1 [internal function]: OCA\\\\DAV\\\\Connector\\\\Sabre\\\\TagsPlugin->handleGetProperties(Object(Sabre\\\\DAV\\\\PropFind), Object(OCA\\\\DAV\\\\Connector\\\\Sabre\\\\Directory))\\n#2 \\/var\\/www\\/system\\/wolke.cweiske.de\\/3rdparty\\/sabre\\/event\\/lib\\/EventEmitterTrait.php(105): call_user_func_array(Array, Array)\\n#3 \\/var\\/www\\/system\\/wolke.cweiske.de\\/3rdparty\\/sabre\\/dav\\/lib\\/DAV\\/Server.php(1059): Sabre\\\\Event\\\\EventEmitter->emit('propFind', Array)\\n#4 \\/var\\/www\\/system\\/wolke.cweiske.de\\/3rdparty\\/sabre\\/dav\\/lib\\/DAV\\/Se

Truncated by Planet PHP, read more at the original (another 11485 bytes)

Categories: Web Technologies

Mocking at architectural boundaries: the filesystem and randomness - Matthias Noback

Tue, 03/06/2018 - 01:27

In a previous article, we discussed "persistence" and "time" as boundary concepts that need mocking by means of dependency inversion: define your own interface, then provide an implementation for it. There were three other topics left to cover: the filesystem, the network and randomness.

Mocking the filesystem

We already covered "persistence", but only in the sense that we sometimes need a way to make in-memory objects persistent. After a restart of the application we should be able to bring back those objects and continue to use them as if nothing happened.

Besides object persistence, managed by object repositories, we also encounter the need to store any number of (unstructured) bytes. A PDF file, an image, a security key, etc. Whenever that need arises, we may reach out to the filesystem, or some external file storage system, and store our data on it.

As soon as an object starts talking to a local or a remote file system, we won't be able to write a unit test for it though. We can't just "mock out" a file system. There are too many details about communicating with the file system that should not go untested. If we would mock, e.g. by overriding built-in functions like fopen() and fwrite(), we might be making lots of assumptions that turn out to be wrong once the software is running on production (e.g. directory doesn't exist, or it isn't writable, the disk is full, etc.).

This is where a tool like vfsStream could help: it replaces the real file system with something that behaves just like an actual file system, leveraging PHP's built-in "stream" abstraction. It might be a useful tool, but I suggest using it only in situations where you can't introduce your own/a better abstraction (e.g. in bad cases of legacy code).

A more powerful alternative for dealing with file systems in a test scenario might be a "file system abstraction" library, like Gaufrette or Flysystem, which does the same kind of thing: it offers abstractions that hold true for all adapter implementations (e.g. FTP, S3, GridFS). Hence, we can mock the filesystem, and trust that Flysystem or Gaufrette will get the actual implementation right.

This is wonderful; we don't have to worry about all the low-level details, in the same way that Doctrine ORM and DBAL deal with a lot of the nitty-gritty details of database communication, on behalf of us. However, even if Doctrine DBAL offers "database abstraction", and Flysystem offers "filesystem abstraction", these aren't our abstractions. Most often, our applications don't really need a database, or a filesystem. They need a way to persist an object, or to store a piece of data.

As described in the previous post, the best solution is to define your needs as an interface first, then implement this interface using the real thing. When you write unit tests for client code of that interface, you're free to create a test double for it. The integration tests you'll write for the implementation of the interface itself, should prove that you made the right assumptions about the third-party code, or the hardware involved. This means that your integration test should test the real thing. In case you use Flysystem with an FTP adapter, your integration test verifies that your code works well with Flysystem and its FTP adapter. So don't replace the FTP adapter with an in-memory adapter or something, since its API may be the same, it'll behave in completely different ways at runtime - ways you want to capture in an integration test.

Mocking "randomness"

Generating random values is another example of something the application itself can't do. Unless it's using predictable randomness, like produced by PHP's mt_rand() function. In that case, although it seems like you can't make that function's return value deterministic in a test, you can actually "seed" the function by providing a number to mt_srand():

$seed = 1000; mt_srand($seed); // will always return 8 $randomNumber = mt_rand(1, 10);

As you can imagine, this isn't "true" randomness. You'd need to ask the computer for such a thing, probably through some convenience method like random_int(). Using that function makes your code unsuitable for a unit test though. That's why you'd need to mock requests for randomness.

Again, start with your own interface. This is a great opportunity to think about what you're really looking for: a random int within a certain range (ask yourself, what would you call this int?), random bytes, something with a random length, etc. Make sure this is reflected in the interface, and provide an implementation for it which uses the underlying, lower-level call to the random number/byte generator.

Mocking "the network"

Maybe you've noticed that I skipped a previously promised discussion about mocking "the network". Since it's a bigger topic, I decided to save it for another article.

Categories: Web Technologies

A Concrete Guide to Dependency Injection - Andrew Embler

Fri, 03/02/2018 - 09:47

If you've done any object-oriented development in PHP, you've probably heard the term Dependency Injection. Still fuzzy on the concept? It's probably simpler than you think. Let's demystify it a bit.
Categories: Web Technologies

PHP 7.2.3 Released - PHP: Hypertext Preprocessor

Wed, 02/28/2018 - 16:00
The PHP development team announces the immediate availability of PHP 7.2.3. This is a security release with also contains several minor bug fixes.All PHP 7.2 users are encouraged to upgrade to this version.For source downloads of PHP 7.2.3 please visit our downloads page, Windows source and binaries can be found on windows.php.net/download/. The list of changes is recorded in the ChangeLog.
Categories: Web Technologies

PHP 7.1.15 Released - PHP: Hypertext Preprocessor

Wed, 02/28/2018 - 16:00
The PHP development team announces the immediate availability of PHP 7.1.15. This is a security fix release, containing one security fix and many bug fixes. All PHP 7.1 users are encouraged to upgrade to this version. For source downloads of PHP 7.1.15 please visit our downloads page, Windows source and binaries can be found on windows.php.net/download/. The list of changes is recorded in the ChangeLog.
Categories: Web Technologies

PHP 5.6.34 Released - PHP: Hypertext Preprocessor

Wed, 02/28/2018 - 16:00
The PHP development team announces the immediate availability of PHP 5.6.34. This is a security release. One security bug was fixed in this release. All PHP 5.6 users are encouraged to upgrade to this version.For source downloads of PHP 5.6.34 please visit our downloads page, Windows source and binaries can be found on windows.php.net/download/. The list of changes is recorded in the ChangeLog.
Categories: Web Technologies

Lasagna code - too many layers? - Matthias Noback

Mon, 02/26/2018 - 01:35

I read this tweet:

"The object-oriented version of spaghetti code is, of course, 'lasagna code'. Too many layers." - Roberto Waltman

— Programming Wisdom (@CodeWisdom) February 24, 2018 Jokes taken as advice

It's not the first time I'd heard of this quote. Somehow it annoys me, not just this one joke, but many jokes like this one. I know I should be laughing, but I'm always worried about jokes like this going to be interpreted as advice, in its most extreme form. E.g. the advice distilled from this tweet could be: "Layers? Don't go there. Before you know it, you have lasagna code..."

It's the same for every lame microservice joke out there; I feel most of those jokes cause people to reject any idea (no matter how good) related to splitting a monolith, using a service architecture (an architecture with services, not necessarily micro ones), etc. Like all things, it won't always be a good choice, but it won't be never a good choice either.

Actually, I think it would be awesome if people would more often consider the option to use some kind of event-driven service-based architecture. Just like I think it would be awesome if people would write more lasagna code, or layered code. Most attempts at layering fail horribly, just like many (most?) attempts at building services fail horribly. Why is that? Because mostly, we have no idea what we're doing. We do know our favorite frameworks by heart. Every configuration option, every little validation constraint, every command-line tool. But we don't know architecture that well.


As I've pointed out before, layers are indeed a great architectural tool for structuring your code. I won't repeat everything here. I just wanted to discuss the concept of a layer a bit more, because this tweet and my own response to it - "Actually, I've never encountered such a project." - received some interesting reactions, for example:

You haven’t? I have some to show you :D. Too many abstractions and services.

— Jelrik van Hal (@jelrikvh) February 25, 2018


I think I have, but it's more about the layers of the call stacks at runtime than how the code is organised. Service A made up of X layers gets injected into service B, which is already made up of Z layers...

— Dave Marshall (@davedevelopment) February 25, 2018 Indirection, not layering

This is actually what I was hinting at: layers, when applied successfully (I'll talk about what I mean by that), are a great thing for a code base. However, most attempts at layering actually result in (not layering, but) indirection. If you'd step-debug through unsuccessfully layered code, you'll taking many steps before you get to the clue of the joke: that all we're doing is bringing out some data, and putting it back in. The complicated steps hiding this simple truth, are the steps of a weird dance between controllers, action helpers, services, tables, data objects and mappers. A lot of indirection, due to mixing in infrastructural concerns at al levels, and often clumsy usage of the programming language.

Trying to trace the workings of this kind of code may feel like you're wading through sticky layers of lasagna. Or maybe, it feels more like peeling of layers of an onion. However, it doesn't mean we should get rid of the concept of a "layer". We only need to realize that what we're looking at really aren't layers.

Successful layering: define the rules

I think it only makes sense. We're trying to deal with the code we're looking at, trying to make it manageable. It's impossible to keep everything in our head at once. We try to follow rules like "skinny controllers", so we push code out of them. We want to prevent historical failures like having mysql_query() calls all over the place, we don't want to mix PHP with html, and then this indirection ("too many layers" a.k.a. "unsuccessful layering") is what happens.

In my experience, successful layering can be achiev

Truncated by Planet PHP, read more at the original (another 2093 bytes)

Categories: Web Technologies

Smoke testing ReactPHP applications with Cigar - Cees-Jan Kiewiet

Sun, 02/25/2018 - 16:00

Last week I came across Cigar, a smoke testing tool by Matt Brunt. Which, to me, is great stepping stone for my personal projects/sites to integration tests. In this post we not only go into Cigar, but also how to start your HTTP ReactPHP application, run cigar against it, and shut it down again. (Note that it doesn't have to be a ReactPHP application it can also be a NodeJS app, or PHP's build in webserver you use for testing.)

Categories: Web Technologies

Move over Graphite, Prometheus is Here - Nomad PHP

Fri, 02/23/2018 - 07:13

May - EU
Presented By
Michael Heap
May 17, 2018
20:00 CEST

The post Move over Graphite, Prometheus is Here appeared first on Nomad PHP.

Categories: Web Technologies

Installing Bolt extensions on Docker - Stefan Koopmanschap

Fri, 02/23/2018 - 05:30

I'm currently working on a website with the Bolt CMS. For this website, I am using an extension. Now, the "problem" with extensions is that they are installed using Composer by Bolt, and end up in the .gitignore'd vendor/ directory. Which is OK while developing, because the extension will just be in my local codebase, but once I commit my changes and push them, I run into a little problem.

Some context

Let's start with a bit of context: Our current hosting platform is a bunch of Digital Ocean droplets managed by Rancher. We use Gitlab for our Git hosting, and use Gitlab Pipelines for building our docker containers and deploying them to production.

The single line solution

In Slack, I checked with Bob to see what the easiest way was of getting the extensions installed when they're in the configuration but not in vendor/, and the solution was so simple I had not thought of it:

Run composer install --no-dev in your extensions/ directory

So I adapted my Dockerfile to include a single line:

RUN cd /var/www/extensions && composer install --no-dev

I committed the changes, pushed them, Gitlab picked them up and built the new container, Rancher pulled the new container and switched it on, and lo and behold, the extension was there!

Sometimes the simple solutions are actually the best solutions

Categories: Web Technologies

Mocking at architectural boundaries: persistence and time - Matthias Noback

Mon, 02/19/2018 - 23:55

More and more I've come to realize that I've been mocking less and less.

The thing is, creating test doubles is a very dangerous activity. For example, what I often see is something like this:

$entityManager = $this->createMock(EntityManager::class); $entityManager->expects($this->once()) ->method('persist') ->with($object); $entityManager->expects($this->once()) ->method('flush') ->with($object);

Or, what appears to be better, since we'd be mocking an interface instead of a concrete class:

$entityManager = $this->createMock(ObjectManagerInterface::class); // ...

To be very honest, there isn't a big different between these two examples. If this code is in, for example, a unit test for a repository class, we're not testing many of the aspects of the code that should have been tested instead.

For example, by creating a test double for the EntityManager, we're assuming that it will work well with any objects we'll pass to it. If you've ever debugged an issue with an EntityManager, you know that this is a bad assumption. Anything may go wrong: a mistake in the mapping, missing configuration for cascading persist/delete behavior, an issue with the database credentials, availability of the database server, network connectivity, a missing or invalid database schema, etc.

In short, a unit test like this doesn't add any value, except that it verifies correct execution of the code you wrote (something a linter or static analysis tool may be able to do as well). There's nothing in this test that ensures a correct working once the code has been deployed and is actually being used.

The general rule to apply here is "Don't mock what you don't own" (see the excellent book "Growing Object-Oriented Software, Guided by Tests", or an article on the topic by Eric Smith, "That's Not Yours"). Whenever I've brought up this rule in discussions with developers, I've always met with resistance. "What else is there to mock?" "Isn't mocking meant to replace the slow, fragile stuff with something that is fast and stable?"

Mock across architecturally significant boundaries

Of course we want to use mocks for that. And we need to, since our test suite will become very slow and fragile if we don't do it. But we need to do it in the right place: at the boundaries of our application.

My reasoning for "when to mock" is always:

  1. If you encounter the need for some information or some action that isn't available to you in the memory of the the currently running program, define an interface that represents your query (in case you need to know something) or command (in case you want to do something). Put the interface in the "core" of your application (in the domain or application layer).
  2. Use this interface anywhere you want to send this query or command.
  3. Write at least one implementation for the interface, and make sure all the clients of the interface get this implementation injected as constructor arguments.
Mocking "persistence"

To fix the EntityManager example above we need to take a step back and articulate our reason for using the EntityManager in the first place. Apparently, we were in need of persisting an object. This is not something the running application could do naturally (the moment it stops, it forgets about any object it has in memory). So we had to reach across the application's boundaries, to an external service called a "database".

Because we always considering reusing things that are already available in our project, we just decided to go with the previously installed EntityManager to fulfill our needs. If however we would've followed the steps described above, we would've ended up in a different place:

  1. I need to persist (not just any object, but) my Article entity, so I define an interface that represents the action I intend for it to do:

    interface ArticleRepository { public function persist(Article $article): void }
  2. I use this interface everywhere in my code.

  3. I provide a default implementation for it, one that uses my beloved EntityManager:

    final class ORMArticleRepository implements A

Truncated by Planet PHP, read more at the original (another 6880 bytes)

Categories: Web Technologies

The Container is a Lie! - Nomad PHP

Sat, 02/17/2018 - 06:57

May - US
Presented By
Larry Garfield
May 17, 2018
20:00 CDT

The post The Container is a Lie! appeared first on Nomad PHP.

Categories: Web Technologies

6 Things I Learned as an Exhibitor at SunshinePHP 2018 - Andrew Embler

Wed, 02/14/2018 - 05:51

Learn why bigger isn't always better.
Categories: Web Technologies

Line Coverage in Unit Tests - Paul M. Jones

Tue, 02/13/2018 - 05:00

The novice says, “I do not strive for 100% line coverage in tests; I only write tests for the code that is important.”

The master says, “If the code is not important, why is it there at all? I will strive to test every line I write; if a line is not important, it should be removed.”

(See also The Way of Testivus.)

Categories: Web Technologies

8 Tips for Improving Bootstrap Accessibility - SitePoint PHP

Mon, 02/12/2018 - 08:00

A few years ago, I wrote about my experiences on developing a Bootstrap version 3 project to be fully accessible for people with disabilities. This focussed mostly on how accessible it is in terms of front-end design. (It didn’t cover accessibility in terms of screen readers, as that’s a whole other story.)

While I could see that the developers behind Bootstrap were making an effort, there were a few areas where this popular UI library fell short. I could also see that there were issues raised on the project that showed they were actively improving — which is fantastic, considering how approximately 3.6% of websites use Bootstrap.

Recently, Bootstrap version 4 was released, so let’s take a look and see if any of the issues I had in the past have improved.

What We’re Looking For with Design Accessibility

There are a few things to consider when designing a website with accessibility in mind. I believe these improve the user experience for everyone and will likely cover a lot of points you would consider anyway.


One way to achieve accessibility is by having a clean, easy-to-use layout that looks good on all devices, as well as looking good at a high zoom level. Up to 200% is a good guide.

Bonus points: having front-end code that matches the layout is also good for users who access the Web with a screen reader or by using a keyboard instead of a mouse.

This allows people to use your website easily irrespective of how they’re viewing it.

Continue reading %8 Tips for Improving Bootstrap Accessibility%

Categories: Web Technologies