Buy
Buy

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Our bundle is missing only two things: it needs a stable release and it needs continuous integration.

Before we automate our tests, we should probably make sure they still pass:

./vendor/bin/simple-phpunit

Bah! Boo Ryan: I let our tests get a bit out-of-date. The first failure is in FunctionalTest.php in testServiceWiringWithConfiguration().

Of course: we're testing the word_provider option, but that doesn't even exist anymore! We could update this test for the tag system, but it's a little tricky due to the randomness of the classes. To keep us moving, just delete the test. Also delete the configuration we added in the kernel, and the loadFromExtension() call. But, just for the heck of it, I'll keep the custom word provider and tag it to integrate our stub word list.

... lines 1 - 26
class KnpULoremIpsumTestingKernel extends Kernel
{
... lines 29 - 40
public function registerContainerConfiguration(LoaderInterface $loader)
{
$loader->load(function(ContainerBuilder $container) {
$container->register('stub_word_list', StubWordList::class)
->addTag('knpu_ipsum_word_provider');
});
}
... lines 48 - 52
}
... lines 54 - 62

The second failure is in KnpUIpsumTest. Ah yea, the first argument to KnpUIpsum is now an array. Wrap the argument in square brackets, then fix it in all three places.

... lines 1 - 8
class KnpUIpsumTest extends TestCase
{
public function testGetWords()
{
$ipsum = new KnpUIpsum([new KnpUWordProvider()]);
... lines 14 - 23
}
... line 25
public function testGetSentences()
{
$ipsum = new KnpUIpsum([new KnpUWordProvider()]);
... lines 29 - 37
}
... line 39
public function testGetParagraphs()
{
... lines 42 - 43
for ($i = 0; $i < 100; $i++) {
$ipsum = new KnpUIpsum([new KnpUWordProvider()]);
... lines 46 - 64
}
}
}

Ok, try the tests again!

./vendor/bin/simple-phpunit

Yes! They pass.

Adding the .travis.yml File

The standard for continuous integration of open source libraries is definitely Travis CI. And if you go back to the "Best Practices" docs for bundles, near the top, Symfony has an example of a robust Travis configuration file! Awesome!

Copy this entire thing, go back to the bundle, and, at the root, create a new file - .travis.yml. Paste!

language: php
sudo: false
cache:
directories:
- $HOME/.composer/cache/files
- $HOME/symfony-bridge/.phpunit
env:
global:
- PHPUNIT_FLAGS="-v"
- SYMFONY_PHPUNIT_DIR="$HOME/symfony-bridge/.phpunit"
matrix:
fast_finish: true
include:
... lines 16 - 59

We'll talk about some of the specifics of this file in a minute. But first, in your terminal, add everything we've been working on, commit, and push.

Activating Travis CI

With the Travis config file in place, the next step is to activate CI for the repo. Go to travis-ci.org and make sure you're signed in with GitHub. Click the "+" to add a new repository, I'll select the "KnpUniversity" organization and search for lorem.

Huh. Not found. Because it's a new repository, it probably doesn't see it yet. Click the "Sync Account" button to fix that. And... search again. There it is! If it's still not there for you, keep trying "Sync Account": sometimes, it takes several tries.

Activate the repo, then click to view it. To trigger the first build, under "More options", click, ah, "Trigger build"! You don't need to fill in any info on the modal.

Oh, and from now on, a new build will happen automatically whenever you push. We only need to trigger the first build manually. And... go go go!

Adjusting PHP & Symfony Version Support

While this is working, let's go look at the .travis.yml file. It's... well... super robust: it tests the library on multiple PHP version, uses special flags to test with the lowest version of your library's dependencies and even tests against multiple versions of Symfony. Honestly, it's a bit ugly, but the result is impressive.

Back on Travis CI, uh oh, we're starting to see failures! No! Let's click on one of them. Interesting... it's some PHP version issue! Remember, we decided to support only PHP 7.1.3 or higher. But... we're testing the bundle against PHP 7.0! We could allow PHP 7.0... but let's stay with 7.1.3. In the Travis matrix, delete the 7.0 test, and change the --prefer-lowest to use 7.1.

... lines 1 - 12
matrix:
... line 14
include:
... lines 16 - 18
- php: 7.1
... lines 20 - 21
# Test the latest stable release
- php: 7.1
- php: 7.2
... lines 25 - 58

Go back to the main Travis page again. Hmm: two failures at the bottom deal with something called symfony/lts. These make sure that Symfony works with the LTS - long-term support version - of Symfony 2 - so Symfony 2.8 - as well as the LTS of version 3 - so Symfony 3.4. Click into the LTS version 3 build. Ah, it can't install the packages: symfony/lts v3 conflicts with symfony/http-kernel version 4.

The test is trying to install version 3 of our Symfony dependencies, but that doesn't work, because our bundle requries everything at version 4!

And... that's maybe ok! If we only want to support Symfony 4, we can just delete that test. But I think we should at least support Symfony 3.4 - the latest LTS.

To do that, in composer.json, change the version to ^3.4 || ^4.0. Use this for all of our Symfony libraries.

... lines 1 - 11
"require": {
... line 13
"symfony/config": "^3.4 || ^4.0",
"symfony/dependency-injection": "^3.4 || ^4.0",
"symfony/http-kernel": "^3.4 || ^4.0"
},
"require-dev": {
"symfony/framework-bundle": "^3.4 || ^4.0",
"symfony/phpunit-bridge": "^3.4 || ^4.0",
"symfony/browser-kit": "^3.4 || ^4.0"
},
... lines 23 - 34

The cool thing is, we don't actually know whether or not our bundle works with Symfony 3.4. But... we don't care! The tests will tell us if there are any problems.

Also, in .travis.yml, remove the lts v2 test.

Ok, find your terminal, add, commit with a message, and... push!

This should immediately trigger a build. Click "Current"... there it is!

Let's fast-forward... they're starting to pass... and... cool! The first 5 pass! The last one is still running and, actually, that's going to fail! But don't worry about it: this is testing our bundle agains the latest, unreleased version of Symfony, so we don't care too much if it fails. But, I'll show you why it's failing in a minute.

Tagging Version 1.0

Now that our tests are passing - woo! - it's time to tag our first, official release. You can do this from the command line, but I kinda like the GitHub interface. Set the version to v1.0.0, give it a title, and describe the release. This is where I'd normally include more details about new features or bugs we fixed. Then, publish!

You can also do pre-releases, which is a good idea if you don't want to create a stable version 1.0.0 immediately. On Packagist, the release should show up here automatically. But, I'm impatient, so click "Update" and... yes! There's our version 1.0.0!

Oh, before I forget, back on Travis, go to "Build History", click master and, as promised, the last one failed. I just want to show you why: it failed because of a deprecation warning:

Referencing controllers with a single colon is deprecated in Symfony 4.1.

Starting in Symfony 4.1, you should refer to your controllers with two colons in your route. To stay compatible with 4.0, we'll leave it.

Installing the Stable Release

Now that we finally have a stable release, let's install it in our app. At your terminal, first remove the bundle:

composer remove knpuniversity/lorem-ipsum-bundle

Wait.... then re-add it:

composer require knpuniversity/lorem-ipsum-bundle

Yes! It got v1.0.0.

We have an awesome bundle! It's tested, it's extendable, it's on GitHub, it has continuous integration, it can bake you a cake and it has a stable release.

I hope you learned a ton about creating re-usable bundles... and even more about how Symfony works in general. As always, if you have any questions or comments, talk to us down in the comments section.

All right guys, seeya next time.

Leave a comment!

  • 2019-01-14 Carlos Eduardo

    Great!! Thank you very much!!! I'll that a look at this multiple kernels architecture.

  • 2019-01-14 weaverryan

    Hey Carlos Eduardo!

    Hmm, actually, this sounds really nice to me :). The downside is that multiple applications means multiple things to deploy and more to manage (e.g. upgrading Symfony versions multiple times, once for each version). Sl obviously, your deploy mechanism will need to be very automated to ease this. Also, it depends on how "big" version 1 needs to be and how many resources you have. For example, if you were a small startup, you might need to reduce the complexity somewhat (maybe). But if you're a bit bigger or already know you will have a certain number of customers, this approach makes more sense.

    So, the downside as I mentioned would just be complexity (a lot of moving pieces to keep track of). You could reduce that by building each application into one project. What I mean is, you would have just 1 Symfony app, but you would have multiple "kernels". This is a bit more advanced, but you would have a "CRM" kernel that boots up all the bundles it needs and runs 100% independent, and another "Finance" kernel that does the same. These would function like separate apps, but living in the same codebase (so you wouldn't need to manage so many shared bundles across many apps, or worry about upgrading Symfony in multiple apps).

    Cheers!

  • 2019-01-10 Carlos Eduardo

    Hey Ryan!! Thanks for another lesson!!

    Since our customers all have their own IT infrastructure, all ERP systems will be deployed separately.
    We're thinking that each module (CRM, Finance, Sales, etc) could be a separated Symfony application, sharing the same database (tables like crm_, fin_, sal_, etc) and one core module to orchestrate the Security (authentication), configuration and the main menu with the links to each application. So it would be like this:

    /erpname-core
    /erpname-crm
    /erpname-finance
    /erpname-sales
    /erpname-etc..

    Then if some customer doesn't need the "sales" module for instance, we just don't deploy it.
    The authentication inside each module could be with the API Token idea.

    Then I'll use the Symfony Bundles only to share codes that would be used by all modules, something like erpname-utilsbundle or erpname-basebundle.

    Can you imagine some negative points in using this approach?

  • 2019-01-10 weaverryan

    Hey Carlos Eduardo!

    Oh boy - yes, this is a great question! The answer... as always is... it depends :p. And there are several good options. The big "it depends" sorta depends on how you want to "host" the app and how data is stored. For example, consider these 2 extremes:

    1) You create one app with one database, and simply segregate everything based on looking up stuff in the database. For example, maybe when you access client1.yourerpsystem.com, you look up the client based on the host name from some Client entity and determine the "look" and enabled features based on flags in there. You more or less just have if statements in your app that hide/show links and deny access to not-enabled features. Pretty much each Entity has a relation back to Client so that you can determine which records are for which client. Or, you can use some tricks to use a different database name for each client to avoid that.

    2) Each Client literally has their own, completely independent Symfony application that is deployed to its own server (or however you deploy) with its own database. There is absolutely nothing shared between different clients - and you even maybe deploy them separately (allowing client A to be on version "1.1" of your site and client B to be on version "1.2").

    These are the 2 extremes - and there are more options kind of "in the middle". What you need depends a lot on your requirements. System (1) is simpler in some way (just one "deployment" and 1 app to manage) but more complex in other ways (need to separate data via the relation to Client).

    A lot of this comes down to: what is the best/worst way to enable/disable features across multiple clients. With situation (2) [or even another version of situation 2 where you have 1 app, but each client has its own kernel with different bundles enabled/disabled]) you *could* enable/disable features by enabling and disabling bundles. One real-world complication to think about is that you would really need to make sure each bundle that can be turned on/off is truly standalone. You would also need to setup your CI system to test the app with all combination of the bundles being on and off, to avoid accidentally relying on some optional feature from a bundle.

    So... I'll stop right there - because a lot of the other pros and cons start to just depend on which type of option you are "leaning" towards. I will say one more thing: the "really cool" (but not really that important, imo) advantage to multiple bundles is that when you disable a bundle, all the routes and services of that bundle are literally not included in your application. That means there is no performance penalty for including unused features in your cache. But, that performance impact may not be that big, and the result is probably more complexity. I have built one of these apps before, and it was more like option (A). It WAS still complex... because feature flags are complex, but it was doable from day 1.

    If you have more questions after all that long talking, let me know!

    Cheers!

  • 2019-01-08 Carlos Eduardo

    Hey!! Great course! Thanks.

    Here's the thing... I watched all this tutorial with the major goal to heal a doubt that I'm facing in the last days. We're rebuilding some corporative systems of our customers and what we're aiming is to build one modular ERP system. You know... it is that traditional concept of share some modules between customers, adding customized functionalities where some of them need, add special modules to others, and there it goes...

    Before I even know exactly what was and what was the purpose of a Symfony Bundle, I was able to swear that bundles was the right approach to build the modules of our new ERP. But now I'm really confused. One thing that made me confused is that way of dealing with routes. It's really harder to build all the routes inside bundles and config the way they're exposed when compared to the annotations way.
    If you could hand me some insights to try to understand what is the best architecture to build this modular ERP with Symfony.
    One way I'm tempted to go is to build each module like a totally separated application, and share only the authentication among them. What do you think?

    Thanks again!!!