I'm developing an app that will consist of multiple Composer packages.
First I have the "main" app (it will be a "project" in Composer) that will contain all the necessary files and folders:
app/
public/
index.php
logs/
config.php
..etc..
This is not an issue. I just set the type as "project" in the composer file so it can be install with composer create-project foo/bar.
I will also build a few optional extensions for the main app. They will be their own Composer packages. This isn't either an issue. I just make them into type "library" and install them with composer install foo/the-extension.
The issue
The extensions will have their own namespaces and some of them will have their own dependencies. A couple of them will even have the same dependencies.
This is needed since all of them will are optional. You can install one or the other or all.
Currently, I've created a new folder called "/dev" in the main app where I have all my extensions while developing. Then, in the main app, I'm loading all the extensions auto loaders:
# Main apps autoloader
require_once __DIR__ . '/vendor/autoload.php';
# Extensions
require_once __DIR__ . '/dev/foo/vendor/autoload.php';
require_once __DIR__ . '/dev/bar/vendor/autoload.php';
...etc...
This works, but it comes with a few drawbacks:
I need to change the code in the main app every time I'm going to make a commit to the main apps repo. This is a hassle and it's easy to miss something
Potential versioning clashes. If two packages depends on the same package that gets a new update. If I miss to update both, there might be a version clash. (This have happened)
It's never good to have more than one auto loader since that can mess things up royally.
So, does anyone know of a proper way of handling this, or is it one of those "well, if it works for you, do it like that"-type of situations?
Been searching for a good solution for this a while now but haven't found any. If I missed some answer here on SO, please mark it as a duplicate and I'll remove this post.
Edit
As #sammitch points out in the answer below, I could add the extensions using Composers "repositories" key. This would remove the multiple auto loader problem. However, that will make the development flow pretty awkward:
You make a change to an extension
You commit and push that change to a git repo
You do a composer update in the main app (to get the new commit)
Now you can test if your changes work
I rather not need to go through all that every single time I make a change the extensions code just to see if the change worked or not.
Whoa whoa whoa, you should only ever have one composer autoloader, and it's a bad idea to just cram in external dependencies like that as it will complicate your dev and/or deployment pipelines later.
What you want to do is have the main project include the subpackages as actual packages. You can do this either by:
1. Pushing them to a git host
https://getcomposer.org/doc/05-repositories.md#loading-a-package-from-a-vcs-repository
{
"repositories": [{
"type": "vcs",
"url": "https://github.com/youruser/yourrepo"}],
"require": {
"youruser/yourpackage": "^1.0.0"
}
}
2. Specifying a local repo
Composer require local package
{
"repositories": [{
"type": "vcs",
"url": "/home/youruser/src/yourrepo"
}],
"require": {
"youruser/yourpackage": "^1.0.0"
}
}
Now you can simply run composer install or composer update youruser/yourpackage and composer will pull in all the necessaries and build the relevant autoloader.
Note: Repository specifications are only effective in the root composer.json, so if your dependencies have specific repo config you'll need to either put that config into the root composer.json, or into your global composer config.
Related
I have a Laravel package I would like to use in my project. I have tested it already and I know I'd need to customize quite a lot of it to fit my needs.
Overriding files in the vendor directory is always quite a chore, especially if you need to touch PHP, Vue, CSS files, etc. Hence, I don't want to go via the usual Composer installation. I also don't care much about future updates to the package, as it already has a solid base.
So the question is: After I download the package files from GitHub and after I place them in their respective directories in my Laravel app - is there a specific list of steps to take in order to make sure the inserted package works? Or is it more like, do it and fix issues one by one until it works?
Cheers.
Composer allows you to choose the local package instead of the remote one.
Create a packages folder in the base directory (the packages folder and Laravel's composer.json file must be in the same directory).
Put your downloaded package into packages (packages/packageName).
Replace the following line in your Laravel's composer.json.
"require": {
by
"repositories": [
{
"type" : "path",
"url" : "./packages/*",
"options" : {
"symLink" : true
}
}
],
"require": {
Run composer require authorName/packageName command in the console.
I'm developing my first Laravel Package, and I realize that my workflow contains much more steps for publishing each modification.
Here is my workflow:
I create a Fresh Laravel 5.4 install. Inside, I create a package/author/name/src where I code my plugin.
I run my tests
Once I have made my change and want to publish it:
a.I push it to my Git rep
b.I tag it with version number
c.In my main project, I run composer update
I check that nothing broke with the integration
This is quite a lot of changes for each little change I want to see in my main project....
Is there a way not to publish any single change, just doing local?
Yes, you can locally develop a package and use it without publishing it. The workflow is: create a packages directory outside of your Laravel project then create the package directory in it, for example the directory structure should look something like this:
- my-laravel-project
- packages
- - my-package-1
- - - composer.json
- - my-package-2
- - - composer.json
Then, in your Laravel project's composer.json file, add the repositories section like given below and add an entry into the require section for each local package, for example:
{
"repositories": [
{
"type": "path",
"url": "../packages/my-package-1"
},
{
"type": "path",
"url": "../packages/my-package-2"
}
],
"require": {
"my/package-1": "*",
"my/package-2": "*"
}
}
In this example, I've two different packages inside packages directory and I've added both packages in the Laravel project. The repositories section describes the package type and path/url for each package so composer can find them locally and require section is simply requiring the dependencies as usual. You may check more on Composer's website.
EDIT
I think I misunderstood satis, now this is my new understanding, please correct me if I am wrong:
In satis.json, I must specify the url s of the packages I want to mirror, e.g the doctrine git repo, yaml git repo... Then satis mirrors all these packages on my server. This would mean I need to add once all packages used within my composer.json of my project to the satis json (around 20 packages/requirements ). Now, I can add my private satis repository to the composer.json file of my project and when running "composer update", it will first look within the satis mirrored packages for the required package. Did I understand that correctly?
Situation:
I am trying to use Satis for private composer repository. I have a private project on github for a website of mine. On the project, I am using composer and therefore I have a composer.json on the root of the project. It looks the following:
{
"name": "Peter North",
"license": "proprietary",
"type": "project",
"autoload": {
"psr-4": {
"": "src/"
}
},
"require": {
"php": ">=5.3.9",
"symfony/http-foundation": "dev-master"
}
...
}
Now I wanted to use my private satis repository of url: packages.ait.company, running on an apache and accessible so far. The satis.json looks the following:
{
"name": "AIT Company",
"homepage": "packages.ait.com",
"repositories": [
{
"type": "vcs",
"url": "git#github.com:north/ait.git" // this is the url of my private github project
}
],
"require-all": true,
"require-dependencies": true,
"archive": {
"directory": "dist",
"format": "tar",
"skip-dev": true
}
}
I think that I did not understand well how to structure the satis.json file and what it needs to contain, because the way I am trying, it does not download the "php" and "symfony/http-foundation" packages that I specified in the composer.json file - though it does download the correct composer.json file of the project into /satis/include directory json file.
How does the satis.json need to look like, when I want to read the composer.json of my project from github and build the private satis repository of the "require entries" ?
In my Satis update script, it is a two step process to create local copies from both external packages as well as internal, private repositories.
The first step only downloads the meta data of external dependencies and puts them into an intermediate satis repository. The configuration explicitly does not "require-all", but has explicit statements of all packages that are being used (and some that may be used, or had been used in the past) with an open-ended version wildcard, i.e. "symfony/console":">=2.4" (no tilde or caret - I want to get major version updates here). Adding require-dependencies:true scans all the dependencies of all the required packages and adds them to the meta data collection as well.
The result is stored in a directory "external".
The second step is responsible for scanning all packages from internal "repositories", and creating archives from them. This obviously is a list of all private repos, but the trick is: You can also add to the satis.json a repository of type "composer" - and this is where I add the external repository with the meta data from external packages. Adding it here adds all the external packages' versions to the list of versions that need to have ZIP files created.
Running it for the first time takes a long time because of all the packages that need to be downloaded (and created locally from the private repos). Running it after that is simply an incremental update of only the new versions that haven't been created previously.
The key is to configure collecting external packages without ZIPs and with explicit version ranges ("*" as version would work, but only use it if you really need all versions) in one Satis file, then add the resulting repository to the second Satis configuration. You cannot combine the two operations (only creating ZIPs of selected external dependencies together with ZIPs of ALL internal repositories) into one Satis run.
One more tip: You probably only want to create local copies of your external dependencies to avoid hitting a Github outage when you are deploying to production (or are developing and need that update). In order to ensure every used dependency is in my Satis, I added both the Satis repository to every composer.json, as well as "packagist":false, to turn off any direct contact to Packagist. You cannot just add a random package then, it has to be in your local Satis repository first.
I have a local project with loaded with Composer libs. I uploaded this project to my FTP and received errors, connected with not found classes from some libs.
Can I simply copy vendor/ folder to FTP or I missed something?
Error received:
Fatal error: Class 'AAA\Core\Tools' not found in /home/aaa/public_html/api.php on line 11
api.php:
<?php
use AAA\Core\Tools;
require_once("./vendor/autoload.php");
require_once("./api/" . Tools::getFieldValue('controller') . ".php");
All works fine on localhost!
Linux has a case sensitive file system. That means that files Alex.txt and alex.txt are the same thing in Windows, but not in Linux. Actually on Linux both can happily live in the same directory:
$ tree .
.
├── alex.txt
└── Alex.txt
0 directories, 2 files
Taking this into consideration, I would try to double check that the paths you are using in your namespace are actually identical to what is found at the file system level. (i.e: AAA directory with only uppercase letters ; Core directory capitalized and Tools.php file capitalized)
If you want to keep your existing file system layout, you can use PSR-4 to explicitly tell Composer how to map the namespace to the file system:
Change autoload section from your composer.json:
{
"autoload": {
"psr-4": {"AAA\\DB\\": "db/"}
}
}
where db/ is the actual path from the file system
Update the autoloader:
$ composer dump-autoload
This will take care of directory names, but doesn't apply for files. Meaning that any file inside db/ must be named exactly as used in namespace (for a usage as use AAA\DB\DBCore the file must be db/DBCore.php).
If your file is named dbcore.php and you want to reference it in your namespace as DBCore, you can use classmap feature from Composer:
"autoload": {
"classmap": [
"db/dbcore.php"
]
}
dbcore.php:
<?php
namespace AAA\DB;
class DBCore
{
}
And you can use it as usual:
<?php
require_once("./vendor/autoload.php");
$dbCore = new \AAA\DB\DBCore();
Firstly I would check the autoloader files composer has generated to make sure the paths are valid on your linux server.
Another simple but common issue is that on windows the folder and file names are not case sensitive however they are on Linux. Double check that the folders and files have the correct case as if not it won't find them to auto load.
Rather than trying to upload via FTP which I think is going to be tricky if not impossible to get right, I would suggest you explore getting composer working on your hosting environment.
Composer is entirely PHP based, so should run anywhere that PHP is running.
If you don't have command line access, you can use something like PHPShell which gives you a PHP based command line on which you can then run Composer.
See this other SO answer to get some tips on how to use PHPShell.
Another option is to build a little PHP wrapper that you actually run by visiting it in your browser, in the classic PHP way. See this other SO answer for some tips on how to do that.
Bottom line, you should really look at getting Composer running on your server rather than trying to bodge it another way.
Once you have done your composer process on the server, you must remove the PHPShell or composer wrapper you created so that you don't leave any security holes.
Did you tell the composer where your Class 'AAA\Core\Tools' is?
You can even add your own code to the autoloader by adding an autoload field > to composer.json.
{
"autoload": {
"psr-4": {"Acme\\": "src/"}
}
}
Composer is not meant to be used that way (i.e. you shouldn't be manually transferring vendor directories from one environment to another).
As you add dependencies to your project, the composer.json file will contain those dependencies. When you run composer install or update on your localhost, it "locks" the current version of those dependencies for your project and stores them in the composer.lock file. You should be transferring the composer.json and composer.lock files from your development environment to your production environment and then running composer install on your production environment as part of your deployment process. When you run composer install on your production environment, Composer will look at your composer.lock file and install the specified versions of the dependencies in the vendor directory of the production environment.
You may want to review the composer documentation regarding basic usage (https://getcomposer.org/doc/01-basic-usage.md) and command line options (https://getcomposer.org/doc/03-cli.md) for more details.
I have this composer.json file
{
"require": {
"filp/whoops": "1.*"
}
}
However, I have a folder for my own project called vendor/imaqtpie/framework/src. This is not hosted anywhere, so if I do composer update to update autoload files, it gives an error.
The requested package "imaqtpie/framework" could not be found in any version, there may be a typo in package name.
I had to add this myself to autoload file to make it work.
'Framework' => array($vendorDir . '/imaqtpie/framework/src')
Is there any way to solve this?
I want to tell Composer that this local vendor folder has to be autoloaded each time regardless of checking server/version, or looking for a more elegant solution since I'm new to composer.
You have to create your own local git repository with your package code to achieve that.
After that, put something like this into your composer.json file.
"repositories": [
{
"type":"vcs",
"url":"/path/to/your/source"
}
],
"require":{
"filp/whoops":"dev-master"
}
Autoloading shouldn't be an issue if you implement it this way...
There are a few ways you can do this.
1.
I'd say the most correct way, is to host it and use Satis to produce a private 'packagist'. Then composer will behave "normally" and get the latest version, do version checking etc. but you say you don't care for this.
If you want more details I can expand on this, I have set up many satis packages and it works really well. (Note there's also the new commercial Toran Proxy which I haven't trialled yet.)
2.
If your 'imaqtpie' library is a fake vendor library (it sounds like you just have some files you've stored there, like you would an old-fashioned include library?), then you could simply use a classmap to point the autoloader to that folder from your top level application. This only makes sense if you are including that folder in your top-level app.
So your app's composer json could look like:
{
"require": {
"filp/whoops": "1.*"
},
"autoload": {
"classmap":[
"vendor/imaqtpie/framework/src"
]
}
}
So this tells composer there's a bunch of classes in that folder. When you run composer dump-autoload it will scan the folder and generate vendor/composer/autoload_classmap.php with all your files listed.
This isn't how you're supposed to use composer, but you're not asking to use composer for package management you're asking how to use composer's autoloader, which I guess is fine! as long as you understand the risks.
3.
If your package is either PSR0 or 4 (it sounds likely from the "src" folder) then you'd similarly do this in your top-level app:
{
"require": {
"filp/whoops": "1.*"
},
"autoload": {
"psr-4": {
"Imaqtpie\\Framework\\":"vendor/imaqtpie/framework/src"
}
}
}
Which again is a bit odd, but should work!
Normally, you'd specify this path in your package's composer.json and then when you do an update it gets merged into your composer.lock and then the vendor/composer/installed.json (which is the source used for the dump-autoload). But in theory you can load whatever you want from the top level app, and therefore you can 'hard code' a package into the vendor library and classpath to it.
I'd probably recommend not doing this though! The vendor folder is a tenuous location which most people and programs would assume can be destroyed and rebuilt at a whim. So it's a dangerous place to store anything which isn't in a package. It's also confusing for any other developers who will assume the same.
So I'd recommend moving your library to another location away from the vendor folder, e.g. 'lib', and then using the classpath approach above to include it in the autoloader.