@param int[] $ints
method annotation. Like this:
public function setInts(int ...$ints): void
{
$this->ints = $ints;
}
When such method would be called it would look like this:
$something->setInts(...[1,2,3]);
That's neat, I tought. I could build quite nice collection classes that would check each type of the input array. And of course I stated working on that as it was one of the ideas I couldn't get out of my head other than implementing it. I also checked github whether someone has not created such library already. And of course, someone has. For example this library was exactly what I had in mind and even had more details that I learned from. Like extending the SplFixedArray which is iterable object of fixed size that uses less memory and it's faster than PHP arrays. Also check other SPL objects if you don't know them already. Or that the author built it immutable and disabled all methods that enabled any mutation. Amazing! I do not understand how come it has only 4 stars.
After 3 evenings of implementing my version of immutable collections into the project I was working on I started thinking whether the conversion from array to a parameter list and back wouldn't slow thing down. So I created a small benchmark.
<?php
class ArrayCollection
{
private $ints;
public function __construct(array $ints)
{
$this->ints = $ints;
}
}
class SplatCollection
{
private $ints;
public function __construct(int ...$ints)
{
$this->ints = $ints;
}
}
class MapTypeCheckCollection
{
private $ints;
public function __construct(array $ints)
{
$this->ints = array_map(function (int $int) {
return $int;
}, $ints);
}
}
class ForeachTypeCheckCollection
{
private $ints;
public function __construct(array $ints)
{
foreach($ints as $int) {
if (!is_int($int)) {
throw new \Exception("{$int} must be type of integer");
}
}
$this->ints = $ints;
}
}
$input = [];
for ($i = 0; $i < 1000000; $i++) {
$input[] = $i;
}
$time = microtime(true);
$memory = memory_get_usage(true);
new ArrayCollection($input);
writeResults(ArrayCollection::class, $time, $memory);
$time = microtime(true);
$memory = memory_get_usage(true);
new SplatCollection(...$input);
writeResults(SplatCollection::class, $time, $memory);
$time = microtime(true);
$memory = memory_get_usage(true);
new MapTypeCheckCollection($input);
writeResults(MapTypeCheckCollection::class, $time, $memory);
$time = microtime(true);
$memory = memory_get_usage(true);
new ForeachTypeCheckCollection($input);
writeResults(ForeachTypeCheckCollection::class, $time, $memory);
function writeResults(string $class, float $time, float $memory): void
{
$timeUsage = microtime(true) - $time;
$memoryUsage = memory_get_usage(true) - $memory;
echo "{$class} created in {$timeUsage} s and used {$memoryUsage} \n\n";
}
A quick overview of what's going on there. I created 4 classes that simulate the collections.
ArrayCollection
- no array element check. It only checks that some array is added. That's how I did it the "old" way.SplatCollection
- the collection that checks each array element with the splat operator. That's what...I like to look at this Google Trends chart, but being lazy human, I'll place it to this article so I don't have to create it every time. I added the ActiveCampaign project as comparison for Mautic as it is also marketing automation tool which started only 4 years before Mautic. Hubspot and Marketo are too old and big for comparison like this.
]]>Everyone needs a hobby, right? I write code for living and I love it. I have the amazing oportunity to build open source software Mautic as my day job. That's something I didn't think was possible a few years back. I was lucky enough to get this opportunity. But building and maintaining open source software have its drawbacks from a programmer's perspecitve. Like that you have to build it with standard technologies like PHP and MySql so users won't have problem to host it themselves. Or that maintainers have to be carful with backward compatibility which makes the progress a bit tedious.
That brings me to my hoby coding project, Cronfig.io. It's not an open source software, but a service. Its goal is to help Mautic users to get it setup easily and keep them informed about the background jobs and if something goes wrong. I don't have to worry about ease of server configuration for users or that there are no web hostings where this app would work. It's under my controll only. And I can learn and use the latest technologies. Which is the fun part for me.
So what technologies did you use to build Cronfig? I'm glad you asked! It's all build in Javascript!
Eh, right when I typed that sentence I realized how boring it sounds. JS is far from latest technology. It is with us for several decades. Let me try again.
For the backend I chose to use NodeJS. It's Google's V8 runtime for running JS on servers. It's the same runtime Chrome browser use. As database Cronfig use MongoDB, which use JSON documents to store data. The "JS" in JSON stands for Javascript too, so even the database could be considered to be part of the JS stack. This database belongs to the NoSQL family of databases so it's again quite different from relational databases like MySQL. New tech to discover, yey!
And for the frontend Cronfig use the amazing ReactJS library build by Facebook with virtual DOM, JSX, live reload and all that cool stuff.
Did that sound less boring? Probably not, but it's exciting for me! It's totally different technology stack than building Mautic. Even the programming paradigm is different. PHP is great for object oriented programming. Well, the new versions of PHP anyway. And JS is quite bad language for OOP, but it's quite good for functional programming. So learning just those functional principles is interesting and fun. Whole new world to discover!
This probject forced me to learn how to configure a server for this JS stack. Some workers are also running on Heroku, so that's different process of deployment compared to VPS. I'm looking towards looking at serverless servicess as it seems it would be great fit for hosting some parts of Cronfig too.
I enjoyed building this simple service at evenings and weekends. And Cronfig is helping people. That's the purpose of every software. There is no other reason to build...
]]>Yes, that's right. I said it. Let me explain what I mean, but first, let's make a few things clear.
You may find interesting to read about why Cronfig was born before this post where I explain why it's stupid, difficult and user unfriendly.
I created Cronfig to be simple and easy to configure. Oppose to other cron configuration solutions. Where you needed to know the syntax, the folder where Mautic lives, the commands. The first version of Cronfig solved all that. You had just a button to push and the cron task started to do what the task description said it will do.
There was a list of available tasks like this:
And when you pressed the button that you wanted to process the emails you saw what the task did, how long it took and when it will be triggered again. You could configure its frequency. Like this:
And I thought it cannot be simpler. Boy, was I wrong. There is still tons of questions users have. Like:
I also spent a lot of time on the UI part where users will see live updates, will be able to configure, stop, refresh, delete the tasks and so on. Let's get rid of all that.
I wrote it in ReactJS as an embeddable UI so anyone could implement it easily into another apps. It was fun, but it was time consuming and totally unnecessary. After several years of looking, I did not find any other self-hosted app that would depend so heavily on cron tasks as Mautic. Other apps need 1 cron tasks for everything they do. No other app do so much work in the background. Let's throw that away. Let's focus only on cron service for Mautic.
When I throw away the UI, how will users configure it?
After several years working with DB and other great coworkers I probably learned to think differently than I used to. The right question to ask is
How to make it intelligent so the users wouldn't have to do anything?
It's not really harder to do. The UI will be a lot simpler. Just a log of what has happened. The rest the information needed to run the right cron tasks at the right interval is stored in Mautic itself. Why should we request that knowledge from the user?
Segments are being rebuilt by a command and the command must be...
]]>foreach
. I've read a great deal that the SplFixedArray is fast and memory efficient over classic PHP array. I wanted to test it with the thought that I can't seem to get out of my head lately: how to create a smart collection in PHP.
I've created simple class with 2 map method implementation. mapMap
is using the array_map
function and the foreachMap
method is emulating the same functionality with foreach
.Here is the benchmark script:
<?php
class MapCollection
{
private $items;
public function __construct(array $items)
{
$this->items = $items;
}
public function mapMap(callable $callable): self
{
return new self(array_map($callable, $this->items));
}
public function foreachMap(callable $callable): self
{
$changes = [];
foreach ($this->items as $key => $item) {
$changes[$key] = $callable($item);
}
return new self($changes);
}
}
class SplMapCollection extends \SplFixedArray
{
public function __construct(array $items)
{
parent::__construct(count($items));
foreach ($items as $key => $item) {
parent::offsetSet($key, $item);
}
}
public function mapMap(callable $callable): self
{
return new self(array_map($callable, $this->toArray()));
}
public function foreachMap(callable $callable): self
{
$changes = [];
foreach ($this->toArray() as $key => $item) {
$changes[$key] = $callable($item);
}
return new self($changes);
}
}
$input = [];
for ($i = 0; $i < 1000000; $i++) {
$input[] = $i;
}
$callable = function (int $item) {
return $item++;
};
$time = microtime(true);
$memory = memory_get_usage(true);
$mapCollection = new MapCollection($input);
writeResults('MapCollection::__construct', $time, $memory);
$time = microtime(true);
$memory = memory_get_usage(true);
$mapMap = $mapCollection->mapMap($callable);
writeResults('MapCollection::mapMap', $time, $memory);
$time = microtime(true);
$memory = memory_get_usage(true);
$mapMap = $mapCollection->foreachMap($callable);
writeResults('MapCollection::foreachMap', $time, $memory);
$time = microtime(true);
$memory = memory_get_usage(true);
$splMapCollection = new SplMapCollection($input);
writeResults('splMapCollection::__construct', $time, $memory);
$time = microtime(true);
$memory = memory_get_usage(true);
$mapMap = $splMapCollection->mapMap($callable);
writeResults('splMapCollection::mapMap', $time, $memory);
$time = microtime(true);
$memory = memory_get_usage(true);
$mapMap = $splMapCollection->foreachMap($callable);
writeResults('splMapCollection::foreachMap', $time, $memory);
function writeResults(string $class, float $time, float $memory): void
{
$timeUsage = microtime(true) - $time;
$memoryUsage = memory_get_usage(true) - $memory;
echo "{$class} processed in {$timeUsage} ms and used {$memoryUsage} \n\n";
}
And here are the results:
MapCollection::__construct processed in 0.00011277198791504 ms and used 0
MapCollection::mapMap processed in 1.0667450428009 ms and used 33558528
MapCollection::foreachMap processed in 1.4232130050659 ms and used 0
splMapCollection::__construct processed in 0.94543695449829 ms and used 16003072
splMapCollection::mapMap processed in 2.1207430362701 ms and used -17555456
splMapCollection::foreachMap processed in 2.3400418758392 ms and used 0
array_map
was way slower in my splat operator post because it creates a new array. Foreach
was just modifying the existing array. If I emulate the same functionality with foreach
where a new array is created, foreach
is slightly slower. In the previous post I stated that I will think twice to use array_map
again. If it's used for the use case is was designed for then it's the best option.
I must be using it wrong. SplFixedArray is way slower compared to just Array. I'll stick with array until I find a good use case or learn how to use SplFixedArray properly.
]]>https://docs.mautic.org/en/setup/how-to-install-mautic/install-and-manage-mautic-with-composer
The original article:
I just tested installing Mautic with Composer and it worked pretty well. Why isn't it the recommended way? Am I missing something? This is a follow up to how I failed installing Mautic with composer as a dependency. We can install Mautic in the same file structure as we are used to now with Composer.
The reason I'm playing with this is because I work on the Mautic Marketplace and I face an issue that all the plugins listed at Packagist are for Mautic 2. They must define what Mautic version they are for otherwise installing such plugin will break the Mautic installation. However, the mautic/core
package is not listed at Packagist so the plugins cannot define the supported Mautic versions.
But let's get back to the other reason why Mautic should be listed at Packagist - how to install Mautic.
At this point if you look at the readme of Mautic you'll see that the recommended way how to install Mautic is to visit https://www.mautic.org/download, fill in the form to get the production zip package and then somehow upload it to the server. Someone will unzip it locally and FTP it one file after another to the server. Someone else will use a command like rsync to upload the zip file and then another command to extract it. Isn't that too much work?
If you want to make changes to Mautic core and then provide the changes to the community or if you are building a plugin, use Git to install Mautic (git clone git@github.com:mautic/mautic.git && composer install
). I want to focus on production installations in this post. Do not use Git to install Mautic in production.
It's as easy as executing this command:
composer create-project mautic/core mautic-dir-name ^2 --no-dev
If composer
is an unknown command, install it first. Replace the mautic-dir-name
with the directory where Mautic should be installed in. The ^2
part means that you want to install the latest Mautic 2 version.
There are unresolved issues like when the production package is being built then some files are being removed as they are not needed in production. Some files like upgrade.php and index_dev,php might be removed for security reasons. But we should be able to do all this in post-create-project-cmd.
]]>Using Composer to install PHP packages is great. Why shouldn't we install Mautic this way? I wanted to make it work mainly because I want to install Mautic plugins via Composer. And installing/updating Mautic with Composer would make it simpler.
It would allow to install and update plugins and themes as well as Mautic the same way - via Composer. And if we'd want to update Mautic and one of the plugins would not support the new Mautic version then Composer would tell us. That's quite useful information to know before the upgrade.
I'll document here what I did and where I got stuck so someone else with more patience could pick it up and continue. It's definitelly possible but it will probably need some changes in Mautic.
You can follow my steps by creating a new directory. Add this composer.json file in it:
{
"name": "mautic/composer-install",
"description": "Creates a new project and installs Mautic as a dependency. Then it will move to the right folder structure.",
"type": "metapackage",
"license": "GPLv3",
"require": {
"mautic/core": "^2"
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/mautic/mautic"
}
],
"minimum-stability": "dev"
}
And then from that folder execute this command: composer create-project
(install Composer if you don't have it yet)
That's all it takes to install Mautic as a dependency. My plan was to add several symlinks to the post-create-project-cmd sections to the index.php, console, etc. It will also need some paths chages to the autoloader as the path is different. But even if I fixed that Mautic did not load.
There would be other issues even if I made it load. Mautic writes quite a lot into its own file structure. Config file, assets, plugins, themes, cache. All those would have to be symlinked or configured outside of the vendor directory.
Edit: A similar and working project already exists: https://github.com/TheDMSGroup/mautic-eb
Edit 2: There is a simple way how to install Mautic via Composer. Just not as a dependency but as its usual file structure.
]]>At mautic.org there already is a section called Marketplace but that's not what I consider a marketplace. It's just a list of plugins.
Everyone probably knows already that monolithical projects are bad and why is that. I'll save you from my rants about monoliths. Is Mautic a monolith? I would say so. It is slightly extendable monolith. If there would be an easy way how to install additional modules then we could break Mautic down into the core and the plugins and users would install the plugins they need. A problem within one plugin would not hold back the release of a new Mautic core version. A problem in one plugin would not require a new Mautic core release that affects everyone, but it would affect only the users who use the plugin. The marketplace should allow real modular architecture.
The marketplace shouldn't also allow users to upgrade a plugin or Mautic core until all modules are compatible so users won't end up with broken Mautic.
One more thought on this topic, imagine iOS or Android without the App Stores. It would be a useless piece of software. App Store is what makes the whole platform valuable.
It's important for security and performance reasons to run the up to date software. We've seen it many times. Mautic is also heavy for integrations with other APIs. It happens way too often that the API version that a Mautic integration supports suddenly ends. It's essential to get an update that supports the new API version to all users in very short period of time. Right now, there is no channel that could do that. The marketplace should be that channel.
Software companies need to get their software and updates to their users reliably. Right now Mautic doesn't allow that. And that's the reason there is not many developers in the Mautic ecosystem. Once there will be a way how to easily install plugins and themes and keep them up to date then professional develoment teams can keep their high level of service. The marketplace should make that possible.
A basic marketplace has 2 parts:
There are other problems to consider.
What if a plugin will need some other dependencies (PHP libraries)? Should they be part of the plugin? If so, there would have to be some build process that each plugin developer would have to take care of.
Hmm, PHP packages, dependency management, package...
]]>37signals had been renamed to Basecamp since the time I've read Getting Real. Basecamp is name of their main product. A project management software. So I bet they gave a good thought into their internal project management processes. I've never used nor saw their product, TBH, but I like the company because of their openness and because they use distributed teams as the one I'm part of and also because I agree with their philosophies.
In Mautic we've tried several agile methodics. Last few months of Mautic, Inc. we were using a variation of kanban. Now we've transformed into scrum under Acquia. ShapeUp is describing another agile framework that Basecamp leaders modified to be effective for them.
Here are some ideas that I find interesting from the book. I will use many direct citations from the book that I've highlighted.
Edit: I also found a good summary of the book by the author Ryan Singer itself at the Full Stack Radio Podcast if you'd rather consume that or know more on the topic.
Estimates start with a design and end with a number. Appetites start with a number and end with a design.
use the appetite as a creative constraint on the design process.
In addition to setting the appetite, we usually need to narrow down our understanding of the problem.
So far I've been in teams where they are given a problem to solve and they estimate how long it would take to finish it (time estimate). Or how how much effort it would take compared to the problems we've solved before (story point estimates). In the book they describe different approach. That is how to tackle a problem within 6 weeks. In other words, they have a fixed time constraint and variable scope.
You can think of the appetite as another part of the problem definition. Not only do we want to solve this use case, we want to come up with a way to do it in X weeks.
Every project manager probably knows well the project management triangle. The boundaries described there are:
The ShapeUp book describes another one:
Based on my experience, the quality part is often overlooked at first stages of every project. The main focus is to make a proof of concept or MVP. Once the concept has been proven and the project is being used by paying customers, the quality becomes a constant that must be top notch and cannot be lowered.
In case of...
]]>Mautic 3 (M3) was announced a few months back but there is no list of specific changes with technical details that should happen in this breaking change (BC) version. My concern is that we plan to make too big changes and we'll never see it finished. M3 should include only the most necessary changes. The breaking changes that we cannot do in a minor release.
Disclamer
I work for Mautic, Inc. but thoughts in this post are my own as a Mautic community member. My goal is to keep the discussion going and provide some specifics that should be base for the M3 version. And what can be done now with M2 already or any time, really.
... but those could be developed in any time. They do not really break backward compatibility.
Missing marketplace is by my opinion the greatest weakness of Mautic ecosystem today. The way the marketplace should work is that in the Mautic administration there will be a list of plugins with description, user rating, comments and the Install button. That's it. I hate how complicated it is right now:
This is terrible. If our objective is to build system for marketers then this isn't it.
And for the plugin developers? There should be central repository where they would git push
their new version and the marketplace would send the notice to all users who have the plugin installed that a new version is available. And again, update with 1 click. Optimally, the marketplace should also make payments for plugins easy. For 3 reasons:
But is this a breaking change? No.
Whenever a change to the config file (app/config/local.php
) is made then Mautic needs the cache cleared to know about the change. That's resource and time consuming and very inconvenient. If the config file would contain only database credentials then the rest of the configuration can be in a database table. No cache clearing needed doing changes.
But is this a breaking change? No. A migration can handle it.
Every developer that starts working on Mautic asks about Twig, entity and route annotations and YAML for config files. The decision to avoid pre-processing from different formats to PHP and use PHP directly was made from the beginning. I know, the PHP version of those different formats is generated and cached once during the cache warmup...
]]>