Introduction
I created a small package that contains ValueObject
type objects for measurement units.
I have a MeasurementUnitValueObject
class (which implement interface ValueObject
), which has two properties:
Value - type float
, and a Unit MeasurementUnit
which is an interface.
It serves me in many projects for units that are treated too detailed in the project to be of a simple type.
For example: I have a project in which I deal in detail with the length unit, so I have created a length classes especially for this, which Length
inherits from MeasurementUnitValueObject
. The class has a field $unit
of type LengthUnit
, which is an Enum implementing MeasurementUnit
. In addition, for each unit, a so-called converter is also created named LengthConverter
, the purpose of which is to convert the unit for a given quantity.
In general, the whole thing comes down to the fact that I can create this kind of code:
$length = new Length(22, LengthUnit::Miles);
$lenght2 = LengthConverter::toKilometers($length);
$lenght->isEqual($lenght2); //true
$lenght2->value // 35.405568;
$lenght2->toMiles()->value; //22
$lenght2->toMiles()->unit; // Enum: LenghtUnit::Miles;
And so on.
My problem
In general, the number of files has grown to quite a large number, and in most projects I simply do not need all of them, and sometimes it happens that, for example, I only need the classes related to Length
, but when I load (To be more specific, by this I mean installing and "wasting" disk memory) the entire package I get 50x more than I need.
In principle, it is not a big problem, because there are generally 3 files for 1 entity: Unit, ValueObject, Converter, and these files are really light, so the whole thing is a matter of a few kilobytes.
However, I was wondering if there is an option in Composer that would allow me to divide one package into smaller segments that can be excluded during installation, and I do not mean creating separate projects.
I would like to still enter during loading:
"require": {
"my_units_package": "^1.0''
}
But in such a way that I could add a tag that specifies what to load or what not to load. I know that there is such an extra
, in which I could specify, for example:
"extra": {
"except": ["Power", "Volume"]
}
or name the tag "only" and pass the entities that it needs.
I know that composer also has the option of running scripts before/after/during various actions, although I don't know how they work, for one. And secondly, I immediately encountered the problem of collisions, which I don't know if it is even possible to solve: What if my project said "load only Length", but I used another one that said "load only Power".
Hence my question, is this type of division of the package into selectable segments possible and How to handle conflicts of this type?
As I said, it's not a big problem for me, so I didn't delve into it, because for now I treat it as academic considerations, although I wonder if anyone has faced such problems and could share their experience/knowledge. Thanks
EDIT
To better understand why I don't want to split each entity into a separate package.
I currently have 50 entities in 1 package. In addition to the classes needed to handle these entities, I have 1x vendor directory, +1 composer.json, +1 composer.lock.
The vendor directory takes up 5mb of disk space. Together with my 50 entities, the whole thing takes up less than 6mb.
When I split it into 50 packages: In each package there will be: 1 entity + 1 vendor +1 composer.json, +1 composer.lock The size of the vendor directory will be unchanged because each package will use exactly the same set of libraries - 5mb.
So in total my 1 project, which was 6mb, will become 50 projects with a total size of 250mb (50 x 5mb).
And with each additional entity the size will grow by 5mb, not by a few kilobytes.
The advantage will be that the projects in which I use this package will be smaller by the number of entities, because there will be 1 vendor directory in them regardless of whether I load 2 entities or 50.
For each project, you need 1 vendor directory, 1x composer.json, 1x composer.lock. Dividing my package into 50 packages means that I will have 50 projects, in which each will have its own 1x vendor, 1x composer.json, 1x composer.lock.
EDIT 2
Lets consider only 2 entities, for example:
- Length
- Money
So by far I have projects:
- EntiesProject
- Project1_thatUseEnties
- Project2_thatUseEnties
- ...
- ProjectN_thatUseEnties
If I split EntiesProject to 2:
- LenghtValueObjectProject
- MoneyValueObjectProject
Each ProjectX_thatUseEnties
which doesn't need one of them
will be smaller by 3 classes for example: MoneyUnit
, MoneyConverter,
Money`
BUT
since EntiesProject
is now separated to 2 projects, each need to have their own composer.json
, composer.lock
and vendor.
If 3 classes have 10 KB data size, it's true that I saved 10 KB each don't needed package IN PROJECT WHERE I USE IT. But I need loss 5 MB because I've created 1 extra project which has 1 vendor directory which has 5 MB size.
Introduction
I created a small package that contains ValueObject
type objects for measurement units.
I have a MeasurementUnitValueObject
class (which implement interface ValueObject
), which has two properties:
Value - type float
, and a Unit MeasurementUnit
which is an interface.
It serves me in many projects for units that are treated too detailed in the project to be of a simple type.
For example: I have a project in which I deal in detail with the length unit, so I have created a length classes especially for this, which Length
inherits from MeasurementUnitValueObject
. The class has a field $unit
of type LengthUnit
, which is an Enum implementing MeasurementUnit
. In addition, for each unit, a so-called converter is also created named LengthConverter
, the purpose of which is to convert the unit for a given quantity.
In general, the whole thing comes down to the fact that I can create this kind of code:
$length = new Length(22, LengthUnit::Miles);
$lenght2 = LengthConverter::toKilometers($length);
$lenght->isEqual($lenght2); //true
$lenght2->value // 35.405568;
$lenght2->toMiles()->value; //22
$lenght2->toMiles()->unit; // Enum: LenghtUnit::Miles;
And so on.
My problem
In general, the number of files has grown to quite a large number, and in most projects I simply do not need all of them, and sometimes it happens that, for example, I only need the classes related to Length
, but when I load (To be more specific, by this I mean installing and "wasting" disk memory) the entire package I get 50x more than I need.
In principle, it is not a big problem, because there are generally 3 files for 1 entity: Unit, ValueObject, Converter, and these files are really light, so the whole thing is a matter of a few kilobytes.
However, I was wondering if there is an option in Composer that would allow me to divide one package into smaller segments that can be excluded during installation, and I do not mean creating separate projects.
I would like to still enter during loading:
"require": {
"my_units_package": "^1.0''
}
But in such a way that I could add a tag that specifies what to load or what not to load. I know that there is such an extra
, in which I could specify, for example:
"extra": {
"except": ["Power", "Volume"]
}
or name the tag "only" and pass the entities that it needs.
I know that composer also has the option of running scripts before/after/during various actions, although I don't know how they work, for one. And secondly, I immediately encountered the problem of collisions, which I don't know if it is even possible to solve: What if my project said "load only Length", but I used another one that said "load only Power".
Hence my question, is this type of division of the package into selectable segments possible and How to handle conflicts of this type?
As I said, it's not a big problem for me, so I didn't delve into it, because for now I treat it as academic considerations, although I wonder if anyone has faced such problems and could share their experience/knowledge. Thanks
EDIT
To better understand why I don't want to split each entity into a separate package.
I currently have 50 entities in 1 package. In addition to the classes needed to handle these entities, I have 1x vendor directory, +1 composer.json, +1 composer.lock.
The vendor directory takes up 5mb of disk space. Together with my 50 entities, the whole thing takes up less than 6mb.
When I split it into 50 packages: In each package there will be: 1 entity + 1 vendor +1 composer.json, +1 composer.lock The size of the vendor directory will be unchanged because each package will use exactly the same set of libraries - 5mb.
So in total my 1 project, which was 6mb, will become 50 projects with a total size of 250mb (50 x 5mb).
And with each additional entity the size will grow by 5mb, not by a few kilobytes.
The advantage will be that the projects in which I use this package will be smaller by the number of entities, because there will be 1 vendor directory in them regardless of whether I load 2 entities or 50.
For each project, you need 1 vendor directory, 1x composer.json, 1x composer.lock. Dividing my package into 50 packages means that I will have 50 projects, in which each will have its own 1x vendor, 1x composer.json, 1x composer.lock.
EDIT 2
Lets consider only 2 entities, for example:
- Length
- Money
So by far I have projects:
- EntiesProject
- Project1_thatUseEnties
- Project2_thatUseEnties
- ...
- ProjectN_thatUseEnties
If I split EntiesProject to 2:
- LenghtValueObjectProject
- MoneyValueObjectProject
Each ProjectX_thatUseEnties
which doesn't need one of them
will be smaller by 3 classes for example: MoneyUnit
, MoneyConverter,
Money`
BUT
since EntiesProject
is now separated to 2 projects, each need to have their own composer.json
, composer.lock
and vendor.
If 3 classes have 10 KB data size, it's true that I saved 10 KB each don't needed package IN PROJECT WHERE I USE IT. But I need loss 5 MB because I've created 1 extra project which has 1 vendor directory which has 5 MB size.
Share Improve this question edited Feb 18 at 3:53 Jason Aller 3,65228 gold badges41 silver badges39 bronze badges asked Feb 16 at 21:20 LordFLordF 4967 silver badges21 bronze badges 21 | Show 16 more comments3 Answers
Reset to default 2Is this type of division of the package into selectable segments possible and How to handle conflicts of this type?
No, it is not possible to divide a package into segments/parts and install only selected segments/parts. If you think each segment can be installed as a separate component, then it should have it's own comoser.json
file. And you should register it as a separate package in Packagist. So someone who needs your entire package can install the entire package and if others need only individual parts they can install those separate packages.
- Your Package (Entire Package)
- Separate Part 1 (Separate Independent Package)
- Separate Part 2 (Separate Independent Package)
- ...
This is how frameworks are built. They have separate packages for different parts of the framework (Routing, Templating etc..). And these separate packages are combined/bundled to form an entire framework.
My suggestion is not to create a problem you don't have - this sounds like premature optimisation, and you're imagining extreme scenarios rather than realistic ones.
As I pointed out in the comments, unused code won't have any run-time impact, because autoloading will ensure that only used classes are actually loaded into memory. So what we're talking about here is download size.
The numbers you are suggesting are 50 to 100 classes, and it sounds like these will be relatively simple - maybe a few hundred lines of code each. Yet in comments you mention a concern that it could reach 10MB - at 50 bytes per line, and 200 lines per file, that's one thousand classes.
Having 1000 packages (or config items of any sort) would create far more effort than it would save, but at the point where you have 1000 classes, I suspect you will be able to identify categories - maybe some are physical measurements (length, area, etc), some are financial, some digital (bytes, etc), some to do with time, etc. Creating a package for each of those categories would then make sense.
As Raghavendra N's answer points out, you can then have a "parent" package which has no code, but depends on all the categories, as a short-hand for loading all of them. Since this can silently replace the existing "monolithic" package, you can cross the bridge when you come to it - if and when the package actually gets too large, create the sub-packages, and go back through your apps using those to save space.
For your original question, the part where you ask:
However, I was wondering if there is an option in Composer that would allow me to divide one package into smaller segments that can be excluded during installation, and I do not mean creating separate projects. ...
Composer offers the type (for installing packages of that) for it.
For what you have in EDIT 2 suggests that you could just use a path repository. There is very little overhead in the projects ./vendor folder for ./vendor/autoload.php and the files from ./vendor/composer only. The rest should be symlinked. So unless you do not put digital waste of composer.lock and additional vendor folders into the packages you require of yours (all these files are superfluous, only a ./composer.json is required) you would keep the number of files at the bare minimum across a system.
But you wrote initially, that you do not want to create additional packages anyway, therefore I do not really understand why in EDIT 2 you provide an example that does.
Given you actually want to have a single library project, there is always the option to use a build manager with a dist target that produces multiple packages out of a single package. You can then add an install target to install them on a system, e.g. in a path or artifact repository (that perhaps already during dist), and publish them on git-lab/hub/XXX and bind them in packagist.. Only one remote repository is needed per package.
Finally Composer has virtual packages ¹ that could circulate around your interfaces. E.g. Given you create the sub-packages distribution with your build manager, then each of those packages could group around your _ interface ValueObject_ and provide theirs for that virtual package only.
It should do without writing, but naturally you can do all this without a build manager and a single repository only.
¹ Creating a new Composer Virtual Package Type
vendor
directory in your application, it contains a copy of each library you use. Maybe you're picturing NPM, and its notoriously giantnode_packages
directory; that's caused by the way JS modules are scoped so that you can have multiple different versions of the same module installed at the same time - PHP doesn't work like that, so Composer couldn't do that even if it wanted to. – IMSoP Commented Feb 17 at 15:22