Contributions to this project are more than welcomed.
In order to have consistency in the quality of the code base, there are rules to follow.
We follow the same Coding Standards that the Laminas Project is using (CodeSniffer).
We use Psalm to perform static analysis on the code.
We use PhpUnit to perform tests. We strive to achieve 100% code coverage in testing.
We use Markdownlint for Markdown Linting, primarily of the README.md file.
We use GitHub actions to perform Continuous Integration to validate that commits comply with requirements. We use the Laminas Continuous Integration Github Action to perform CI on Pull Requests.
The Laminas CI action will perform the following actions:
- Markdown Linting of the
README.mdfile - Coding Standard checks
- Static Analysis checks
- Unit Testing against all PHP version that the packages supports for both lastest and lowest versions of the dependencies.
If you are working on new features or refactoring for an existing project item, create a Request For Comments (RFC) in the corresponding repository via a new issue report. The objective is to generate discussion and comments before initiating work.
If you want to propose a new component/package, create a RFC via a new issue report in the LM-Commons Github repository
Each repository uses release branches named after the minor release series they represent; e.g., "2.11.x", "3.1.x", "0.2.x", etc. Each branch is under a "security window", and can and will receive security updates as long as the minimum supported version of PHP in that branch is still supported by php.net. Generally speaking, the default branch is targeting the next minor or major version, and does not yet have releases against it; it represents new features or changes to the package. The release branch with the next most recent release series is the one for the currently active release, and will receive bugfixes and security fixes, but no new features. Since each component has its own lifecycle, the number and names of releases branches will vary from component to component.
For example, assuming the latest release of a component is 1.1.0, then:
- Branch 2.0.x corresponds to the next major release, if any.
- Branch 1.2.x, typically the default branch, corresponds to the next minor release with new features
- Branch 1.1.x corresponds to the branch receiving bugfixes and patches for release 1.1.0 which will be eventually released as 1.1.1
If you want to contribute to an existing repository, here is the recommended workflow:
-
Your first step is to establish a public repository from which we can pull your work into the canonical repository. We recommend using GitHub, as that is where the component is already hosted.
-
Setup a GitHub account, if you dont' have one.
-
Fork the repository using the "Fork" button at the top right of the repository landing page. It is recommended that you include all branches in your forked repository
-
Clone the forked repository into your local development environment. Use the "Clone or download" button above the code listing on the repository landing pages to obtain the URL and instructions.
-
Navigate to the directory where you have cloned the repository.
-
Add a remote to your fork; substitute your GitHub username and the repository name in the commands below.
$ git remote add fork git@github.com:{username}/{repository}.git $ git fetch fork
Alternately, you can use the GitHub CLI tool to accomplish these steps:
$ gh repo clone {org}/{repo}
$ cd {repo}
$ gh repo forkPeriodically, you should update your fork or personal repository to match the canonical repository. Assuming you have setup your local repository per the instructions above, you can do the following:
$ git fetch origin
$ git switch {branch to update}
$ git pull --rebase --autostash
# OPTIONALLY, to keep your remote up-to-date -
$ git push fork {branch}:{branch}If you're tracking other release branches, you'll want to do the same operations for each branch.
We recommend you do each new feature or bugfix in a new branch. This simplifies the task of code review as well as the task of merging your changes into the canonical repository.
A typical workflow will then consist of the following:
- Create a new local branch based off the appropriate release branch.
For example,
hotfix/9295from branch 1.1.x for a fix on release 1.1.0 - Switch to your new local branch.
(This step can be combined with the previous step with the use of
git switch -c {new branch} {original branch}, or, if the original branch is the current one,git switch -c {new branch}.) - Do some work, commit, repeat as necessary.
- Push the local branch to your remote repository.
- Send a pull request.
The mechanics of this process are actually quite trivial. Below, we will create a branch for fixing an issue in the tracker.
$ git switch -c hotfix/9295
Switched to a new branch 'hotfix/9295'... do some work ...
To ensure that your work will pass the CI tests, run the QA tools:
$ composer test
$ composer cs-check
$ composer static-analysis
# if you modified the README.md file
$ npx markdownlint-cli2 ./README.mdOnce there are no more errors reported, you are ready to commit you changes.
Psalm will perform static analysis and signal potential issues that must be resolved or baselined in order to pass CI when commits are pushed to GitHub.
Ideally, you should fix the code such where Psalm raises an issue. However, this is not always possible for many reasons. Issues that cannot be resolved will need to be either baselined or suppressed.
First, when running Psalm, ensure that the existing baseline, if any, is updated following your changes:
$ composer static-analysis -- --update-baseline
or
$ composer static-analysis-update-baselineTo baseline remaining issues, which basically tells Psalm to ignore, for now, the errors it reports without modifying the code:
$ composer static-analysis -- --set-baseline=psalm.baseline.xmlTo suppress an error, which basically tells Psalm to ignore an error permanently, you can add instructions in the code for Psalm to ignore specific errors:
/** @psalm-suppress MixedArgument */
return new MyService($container->get('Some dependency'));Use @psalm-suppress carefully and scarcely to avoid suppressing errors that should be caught.
$ git commit --signoffSee the section on commit signoffs below for more details on the
--signoffoption togit commitand why we require it.
... write your log message ...
$ git push fork hotfix/9295:hotfix/9295
Counting objects: 38, done.
Delta compression using up to 2 threads.
Compression objects: 100% (18/18), done.
Writing objects: 100% (20/20), 8.19KiB, done.
Total 20 (delta 12), reused 0 (delta 0)
To ssh://git@github.com/{username}/{lmc-package-name}.git
b5583aa..4f51698 HEAD -> hotfix/9295To send a pull request, you have several options.
If using GitHub, you can do the pull request from there. Navigate to your repository, select the branch you just created, and then select the "Pull Request" button in the upper right. Select the user/organization "lm-commons" as the recipient.
You can also perform the same steps via the GitHub CLI tool.
Execute gh pr create, and step through the dialog to create the pull request.
If the branch you will submit against is not the default branch, use the -B {branch} option to specify the branch to create the patch against.
Make sure that you submit your pull request against the appropriate branch in the upstream repository.
We distinguish assertions in two categories:
- Expectations of something that is always supposed to be true
- Expectations of some input to follow some rule
The former is a development-only concern, aiming to aid tools (e.g. IDEs, static analysis) to perform a more accurate type detection.
In this scenario, you should use the assert() function, which executes the expression when zend.assertions is enabled.
That will make sure that the condition is valid, while easing future refactoring.
The latter is something that must be executed regardless of configuration or environment.
These expectations should be configured using the class Webmozart\Assert\Assert, which provides an extensive list of helpful methods for input validation.
Please make sure to add a helpful message to be used when the assertion fails.
Example:
<?php
declare(strict_types=true);
namespace Lmc\Examples;
use Psr\Container\ContainerInterface;
use Webmozart\Assert\Assert;
$container = require __DIR__ . '/../config/container.php';
// we know that the file above ALWAYS returns an instance of this type
\assert($container instanceof ContainerInterface);
// now we can call `ContainerInterface#get()`, which will be understood by psalm and even autocompleted by PHPStorm
$app = $container->get(MyApp::class);
// we cannot always rely that the `MyApp::class` service is indeed an instance of that class, hence an assertion
// that guards it despite the environment
Assert::isInstanceOf(
$app,
MyApp::class,
'It looks like the DI container is misconfigured, please verify the service ' . MyApp::class
);
$app->run();Which branch should you issue a pull request against?
- For fixes against the stable release, issue the pull request against the release branch matching the minor release you want to fix.
- For new features, or fixes that introduce new elements to the public API (such as new public methods or properties), issue the pull request against the default branch.
As you might imagine, if you are a frequent contributor, you'll start to get a ton of branches both locally and on your remote.
Once you know that your changes have been accepted to the canonical repository, we suggest doing some cleanup of these branches.
-
Local branch cleanup
$ git branch -d <branchname> -
Remote branch removal
$ git push fork :<branchname>
The documentation for each repository is available in the "docs/" directory of that repository, is built using Docusaurus. To learn more about how to contribute to the documentation for a repository, including how to setup the documentation locally, please refer to https://github.com/LM-Commons/documentation-theme.
In order for us to accept your patches, you must provide a signoff in all commits; this is done by using the -s or --signoff option when calling git commit.
The signoff is used to certify that you have rights to submit your patch under the project's license, and that you agree to the Developer Certificate of Origin.
You can automate adding your signoff by using a commit template that includes the line:
Signed-off-by: Your Name <your.email@example.com>
Put that line into a file, and then run the command:
git config commit.template {path to file}If you want to use this template everywhere, pass the --global option to git config.
Many IDEs, such as PhpStorm, supports signoff with their GIT commands.
You can add a signoff manually when committing from the GitHub website by adding the line Signed-off-by: Your Name <your.email@example.org> by itself in the commit message.
If you have forgotten to signoff your commits, you have two options. For both, the first step is determining the number of commits you have made in your patch. On Unix-like systems (Linux, BSD, OSX, WSL, etc.), this is generally accomplished via:
git log --oneline {original branch}..HEAD | wc -lFrom there, choose either to only signoff, or perform an interactive rebase:
-
Signoff only: run
git rebase --signoff HEAD~{number you discovered} -
Full interactive rebase: run
git rebase -i HEAD~{number you discovered} -x "git commit --amend --signoff --no-edit". This will present the standard interactive rebase screen, but include the lineexec git commit --amend --signoff --no-editbetween each commit. Leave those lines after each commit you will be keeping.
If you run into issues during a rebase operation, you can generally execute git rebase --abort to return to the original state.
When done, execute git push --force-with-lease, specifying the correct remote and branch, in order to force-push your amendments.