Looking at the Packagist registry, we can see that most packages follow a pattern, with some small changes to fit their needs, while others have a weird folder structure that takes time to get your head around.
This problem has been solved in different ways by different people. Some frameworks have their own recommendations on how to structure your classes and assets, while others have a skeleton that you can use as a starting point. In this article, we’re going to explore the php-pds/skeleton and use it to build a small package as a demonstration.
Note: PDS stands for Package Development Standards.
What We’re Building
The idea is to have a way to map FAQ pages (or any other page) to exceptions thrown by our Laravel application. This will serve as a starting point for our users to work out what went wrong.
You can find the final code on GitHub. Feel free to send pull requests to improve it or suggest fixes! (If your Git is rusty, we have a good premium course for that)
PDS Skeleton
The PDS skeleton recognizes that there are already some widespread practices in use by developers who publish on Packagist. After some research using Packagist’s API to explore common folder structures, the author came up with an interesting summary.
When we publish something publicly, it must adhere to some common rules like being tested, documented, formatted, etc. The next step is to have a common folder structure to help developers and contributors understand the code flow.
Surely you’ve heard of the PHP league before! If you want to contribute a package to the league, you must be using their skeleton along with some other rules to be followed. I think having the same thing for the whole community would help improve the readability and consistency of all publicly released packages.
Building the Package
Since this is a Laravel package, I will be following this workflow. Go ahead and download the PDS skeleton package.
cd vendor
mkdir whyounes # this is my author namespace
curl -LOk https://github.com/php-pds/skeleton/archive/1.0.0.zip # Download file
unzip 1.0.0.zip
mv 1.0.0 laravel-faq-exceptions
rm 1.0.0.zip
We need to update our composer.json
file so that it can be discovered by Composer when loading dependencies. Let’s keep it simple for now!
{
"name": "Whyounes/laravel-faq-exceptions",
"type": "library",
"description": "Laravel package for mapping exception FAQ pages.",
"homepage": "https://github.com/Whyounes/laravel-faq-exceptions",
"license": "MIT",
"require": {
"laravel/framework": "~5.3"
},
"require-dev": {
"pds/skeleton": "~1.0"
},
"autoload": {
"psr-4": {
"Whyounes\\FaqException\\": "src/"
},
"classmap": [
"tests"
]
},
"autoload-dev": {},
"bin": []
}
You may have noticed that the require-dev
section contains the pds/skeleton
package. This has two benefits.
- It helps track who’s using the skeleton, and developers can check it to see the naming rules.
- It includes the command line helper to generate and validate our skeleton.
Now that we have a starting point, we can init the repo and add our Git remote.
git init
git add .
git commit -m "first commit"
git remote add origin git@github.com:Whyounes/laravel-faq-exceptions.git
git push -u origin master
Naturally, replace the remote URL with your own repo’s URL.
The skeleton contains some classes by default as a demonstration, so go head and clean the directories.
Alternative Option
There is another way to generate the skeleton without downloading the zip file and unzipping it.
composer require --dev pds/skeleton
./vendor/bin/pds-skeleton generate
This will generate all missing files and folders.
Config
Usually, a package has some configuration files to rely on when deciding what to do. The PDS skeleton has a directory for this called config
.
If the package provides a root-level directory for configuration files, it MUST be named
config/
. This publication does not otherwise define the structure and contents of the directory.
We only have one config file for now. We can put it in there, and we will refer to it in our service provider (more about this later).
Resources
Our package needs to have some DB access to be able to map exceptions to FAQ pages. This means we need to create a migration for this purpose.
PDS Skeleton has a resources
directory that is described as:
If the package provides a root-level directory for other resource files, it MUST be named
resources/
. This publication does not otherwise define the structure and contents of the directory.
The rules don’t define how the underlying structure should look. Currently, we’ll use it to store our migrations
and views
. It can also be used to store seeders, language files, etc.
// resources/migrations/2014_10_12_000000_create_faq_table.php
class CreateFaqTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('whyounes_faq', function (Blueprint $table) {
$table->increments('id');
$table->text('exception');
$table->string('codes');
$table->text('url');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('whyounes_faq');
}
}
We’re also going to need a view file to display the thrown exception:
// resources/views/faq.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Laravel</title>
<!-- Fonts -->
<link href="https://fonts.googleapis.com/css?family=Raleway:100,600" rel="stylesheet" type="text/css">
<!-- Styles -->
<style>
html, body {
background-color: #fff;
color: #636b6f;
font-family: 'Raleway', sans-serif;
font-weight: 100;
height: 100vh;
margin: 0;
}
</style>
</head>
<body>
<div class="flex-center position-ref full-height">
<div class="content">
<div class="links">
An error has occured: {{ $exception->getMessage() }}
@if(!is_null($faq))
<br>You can get more details about the problem <a href="{{ $faq->url }}">here</a>.
@endif
</div>
</div>
</div>
</body>
</html>
Source Files
Ok, now we get to the actual package logic. The skeleton has a src
directory that is described in the documentation as:
If the package provides a root-level directory for PHP source code files, it MUST be named
src/
. This publication does not otherwise define the structure and contents of the directory.
The src
directory is obviously the place where we store our package’s source code, and, as before, the specification doesn’t describe the underlying structure.
The actual source code is in the repo, but let’s briefly discuss these classes.
src/Models/Faq.php
: This is the model class for interacting with our DB table. We group models under the same directory here.src/Providers/FaqProvider.php
: The provider will hold our package’s service providers. We use it to publish assets (like views, migrations, config), register IoC bindings, routes, etc.src/Repositories/FaqRepository.php
: This is a repository for interacting with our Eloquent models. I prefer to keep them separate from the models directory.
We also have some classes at the root of the src
directory. This is not violating any rules of the skeleton. In fact, it makes sense for some classes to be in the root of the namespace.
How Does the Package Work?
The user will use one of the renderers depending on the application type. The WebRenderer
class will render the exception using the faq.blade.php
template. The ApiRenderer
will return a JSON response containing the exception details.
The typical usage will look like this:
// app/Exceptions/Handler.php
class Handler extends ExceptionHandler
{
// ...
public function render($request, Exception $exception)
{
if ($request->expectsJson()) {
$renderer = App::make(ApiRenderer::class);
} else {
$renderer = App::make(WebRenderer::class);
}
return $renderer->render($exception);
}
// ...
}
Tests
The skeleton also contains a tests
folder, which we’ll use for our package’s tests.
If the package provides a root-level directory for test files, it MUST be named
tests/
. This publication does not otherwise define the structure and contents of the directory.
The classes we have to test here are the WebRenderer
, ApiRenderer
and Models/Faq
. Even though the specification doesn’t specify the underlying structure of the tests
folder, I advise you to make it identical to your src
folder, which makes searching for tests and locating them easy and predictable.
I think that this should be RECOMMENDED by the skeleton spec as a best practice. Most packages do this and it seems logical.
You can check out the repo on GitHub to see the actual test code.
Public
Our package doesn’t have any public assets that need to be accessed by the user. But in case you have some in yours, make sure to put them here.
If the package provides a root-level directory for web server files, it MUST be named
public/
. This publication does not otherwise define the structure and contents of the directory.
The confusing part here is whether to use the /resources
directory or the /public
one. The answer here is “it depends”. If we’re building a package for a framework like Laravel, we have the right to specify assets to be published by the framework into the framework’s own /public
folder. However, if we’re using the skeleton for a full application, we can use the /public
folder as the web root.
Bin
Some packages provide executable files to accomplish a task. In such cases, the /bin
directory might be useful. For example, packages like PHP Code Sniffer are ideal candidates for this (but the developer in this case chose to call the folder scripts
¯\_(ツ)_/¯ )
Documentation
Documentation is an essential part of every application/package. It is the go to place for software consumers and developers to get an idea of how things work, and what they’re doing wrong.
If the package provides a root-level directory for documentation files, it MUST be named
docs/
. This publication does not otherwise define the structure and contents of the directory.
PDS skeleton provides a separate directory for the documentation that we can structure however we want. The best approach, in my opinion, is to separate it into topics and have them in separate directories. In the case of an API, we can separate it into endpoints.
README
The README file is very important, but not officially a part of /docs
. It gives users an overview of how to install and integrate your code, along with some other general details. Our README file looks like this.
You can see that the file only contains a few details about the package; it’s just an overview, no need to make it too long. The docs
directory should contain the rest of the in-depth information.
License
Not many of us care about the package license, but it’s still an important step before publishing anything. Someone may want to use the package in unexpected ways, and they need to know if this action is permitted or not.
This website has a really nice overview of the different available licenses. For our package, we will use the MIT license.
Contributing
Another part that is often ignored by developers is the contribution guideline. The application must have a clean process for other developers to contribute features and fixes.
Note: GitHub automatically links to this file when someone is making a new pull request.
We don’t have to create one from scratch. We can chose one that suits our needs and copy it. The file should include:
- how to submit bug reports and fixes.
- how to submit security fixes.
- which coding style is used.
Other things may be included, but this should be enough to get contributors started.
Thanks for choosing to contribute to this package.
## Bug Fixes
Bug fixes for a specific release should be sent to the branch containing the bug. You can also submit a failing test showing where the bug occurs.
## Security Fixes
Security fixes may be sent as a PR or via email to mymail@example.com
## Coding Standard
This package follows the PSR-2 coding standard and the PSR-4 autoloading standard.
Changelog
The changelog file is used to track changes and bug fixes between releases. It’s easier to look at a file than to scroll over Git commits to see what happened. An example could be something like this:
# Changelog
## 1.0.37 - 2017-03-22
### Fixed
* Space escaping for Pure-FTPd in the FTP adapter.
## 1.0.36 - 2017-03-18
### Fixed
* Ensure an FTP connection is still a resource before closing it.
* Made return values of some internal adapters consistent.
* Made 0 a valid FTP username.
* Docblock class reference fixes.
* Created a more specific exception for when a mount manage is not found (with BC).
## 1.0.35 - 2017-02-09
### Fixed
* Creating a directory in FTP checked whether a directory already existed, the check was not strict enough.
## 1.0.34 - 2017-01-30
### Fixed
* Account for a Finfo buffer error which causes an array to string conversion.
* Simplified path handling for Windows.
// ...
The above snippet is taken from the PHPLeague’s flysystem package.
Validation
We can use the validate
command to see if our structure respects the PDS rules.
./vendor/bin/pds-skeleton validate
Conclusion
Using a documented skeleton will not just help you organize your files, but it also helps others predict where things are or should be. The PDS skeleton is not the only one that exists out there, but others like the PHPLeague skeleton have already been adopted by a large number of developers, and it’s PDS compliant by default. This means that it respects the same rules and is considered valid when being checked against the validator.
Do you use a skeleton when creating a new application or package? Do you think we should have a wide-reaching standard for this too? Let us know in the comments!
Frequently Asked Questions (FAQs) about PDS Skeleton
What is the PDS Skeleton and why is it important in PHP development?
The PDS Skeleton is a standard for file and folder structure in PHP development. It provides a consistent and organized structure that makes it easier for developers to understand and navigate through the project. This is particularly important in large projects where multiple developers are involved. By following the PDS Skeleton, developers can easily locate files and understand their purpose, which can significantly improve productivity and efficiency.
How does the PDS Skeleton compare to other file and folder structures?
The PDS Skeleton is designed specifically for PHP development, taking into account the unique requirements and best practices of PHP programming. Unlike other file and folder structures, the PDS Skeleton provides a comprehensive and detailed structure that covers all aspects of a PHP project, from the source code to the tests and documentation. This makes it a more effective and efficient tool for PHP developers.
How can I implement the PDS Skeleton in my PHP project?
Implementing the PDS Skeleton in your PHP project involves organizing your files and folders according to the PDS Skeleton structure. This includes creating directories for the source code, tests, and documentation, and placing the corresponding files in the appropriate directories. It also involves following the naming conventions and other guidelines provided by the PDS Skeleton.
What are the benefits of using the PDS Skeleton?
Using the PDS Skeleton provides several benefits. It improves the organization and readability of your code, making it easier for other developers to understand and contribute to your project. It also enhances the maintainability of your project, as the structured layout makes it easier to locate and update files. Furthermore, it promotes best practices in PHP development, which can improve the quality and reliability of your code.
Can the PDS Skeleton be used in other programming languages?
While the PDS Skeleton is designed specifically for PHP development, the principles and concepts it promotes can be applied to other programming languages. However, the specific structure and guidelines may need to be adapted to fit the requirements and best practices of the particular language.
What is the relationship between the PDS Skeleton and the PSR standards?
The PDS Skeleton and the PSR standards are both aimed at promoting best practices in PHP development. While the PSR standards focus on coding style and conventions, the PDS Skeleton focuses on file and folder structure. Both are complementary and can be used together to improve the quality and maintainability of PHP projects.
How does the PDS Skeleton handle third-party libraries and dependencies?
The PDS Skeleton provides a dedicated directory for third-party libraries and dependencies. This allows developers to easily manage and update these components without affecting the rest of the project. It also makes it easier to isolate and resolve issues related to these components.
Can I customize the PDS Skeleton to fit my project’s needs?
Yes, the PDS Skeleton is designed to be flexible and adaptable. While it provides a standard structure, developers are free to modify and extend it to fit their project’s specific needs and requirements. However, it is recommended to follow the general principles and guidelines of the PDS Skeleton to maintain consistency and readability.
What tools can I use to implement the PDS Skeleton?
There are several tools available that can help you implement the PDS Skeleton in your PHP project. These include IDEs that support the PDS Skeleton, as well as command-line tools that can generate the necessary files and directories. You can also use build tools and scripts to automate the process.
Where can I find more information and resources about the PDS Skeleton?
You can find more information and resources about the PDS Skeleton on the official PDS Skeleton GitHub page. This includes the full specification, examples, and tutorials. You can also find discussions and articles about the PDS Skeleton on various PHP development forums and blogs.
Younes is a freelance web developer, technical writer and a blogger from Morocco. He's worked with JAVA, J2EE, JavaScript, etc., but his language of choice is PHP. You can learn more about him on his website.