Re: Initializing constants once, with code?
| From: | Mike Schinkel | Date: | Sat, 04 Jan 2020 21:25:33 +0000 |
| Subject: | Re: Initializing constants once, with code? | ||
| References: | 1 2 3 4 5 6 7 8 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-107986@lists.php.net to get a copy of this message | ||
> On Jan 3, 2020, at 2:52 PM, Larry Garfield <larry@garfieldtech.com> wrote:
>
> There's two broad reasons I think this is a bad idea.
>
> 1) Constants are one thing. Function calls are another. They serve different purposes.
> Trying to mutate them into one thing can only lead to confusion and lack of understanding about what
> is actually going on.
>
> 2) The approach you describe (of starting with constants everywhere and refactoring to method
> calls later)... I would never do and do not endorse. What you describe is basically "make
> globals nicer to work with", whereas I am 100% firmly in the camp of "if I could remove
> globals from the language entirely I would". Frankly, the use of constants for configuration
> is an anti-pattern to begin with; they should be used only for things that are truly constant.
> Honestly, I cannot recall the last time I used constants for anything other than giving some other
> compile-time value a nicer name. (Eg, DEFAULT_THING_VALUE or giving nice names to bit flags or
> something like that.)
>
> For configuration, my answer is frankly "put your configuration behind a nice
> configuration object from the very beginning and then you won't have to refactor it later;
> Problem solved." You can use env vars for configuration, and wrap those into a nice object,
> possibly using one of the many DotEnv implementations that already exist to make them nicer to work
> with in development. That is superior in basically every conceivable way to semi-mutable globals
> passing themselves off as pseudo-constants.
>
> I *would* love to see property accessors come back, which would have a side effect of making
> what you describe a little easier, but at no point is it pretending to be a compile time value when
> it isn't.
>
> --Larry Garfield
<sigh> So much to unpack here, but I will just hit the most important points.
You can say "Problem solved" but only because you redefined the problem into an
idealistically convenient use-case:
1. IF you have full control of all the code,
2. IF you are starting from scratch and do not have legacy code to contend with,
3. And IF you and your team are not limited by budget, timeline or developer skill.
In the real world however you don't often get those ideal scenarios.
It is irrelevant if you would never use constants or not endorse the approach because many
developers have used constants in the past and continue to do so. I am proposing a solution to
address a problem and you are proposing idealogical purity.To give an analogy, it would be like me
asking Congress to address teen pregnancy and you being a congressman who first spoke and said
"Let's tell them not to have sex. Problem solved."
But we can argue *opinions* all day long. Let me provide some real-world examples and some numbers.
Symfony — which many PHP developers hold in high regard including Drupal developers — uses
constants:
https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php
https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Intl/Locale/Locale.php
https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Intl/DateFormatter/IntlDateFormatter.php
https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Workflow/TransitionBlocker.php
https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Intl/Intl.php
https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Security/Core/Security.php
https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php
On the other end of the spectrum are WordPress plugins. I maintain a *somewhat* complete mirrors of
the plugins database from WordPress.org which I download from here http://plugins.svn.wordpress.org/.
I generated a list of
const <symbol> = found across all plugins using this
command:
grep "const\s+[a-zA-Z1-9_]+\s*=" -RE --include \*.php . > const.txt
This produced a file containing 455,068 constants. That's a lot of constant usage for
something you argue should never be done.
Using this command I calculated that there were 338,520 constants that were NOT integer or float.
cat const.txt | grep -E "=\s*-?\d+(\.\d+)?\s*;" | wc -l
A lot of them (~20%?) were to create array keys that could be referenced in a type-safe manner, e.g.
from Guzzle:
From 1-click-migration/vendor/guzzlehttp/guzzle/src/RequestOptions.php
const ALLOW_REDIRECTS = 'allow_redirects';
const AUTH = 'auth';
const BODY = 'body';
const CERT = 'cert';
const COOKIES = 'cookies';
const CONNECT_TIMEOUT = 'connect_timeout';
const DEBUG = 'debug';
const DECODE_CONTENT = 'decode_content';
const DELAY = 'delay';
const EXPECT = 'expect';
const FORM_PARAMS = 'form_params';
const HEADERS = 'headers';
const HTTP_ERRORS = 'http_errors';
const JSON = 'json';
const MULTIPART = 'multipart';
const ON_HEADERS = 'on_headers';
const ON_STATS = 'on_stats';
const PROGRESS = 'progress';
const PROXY = 'proxy';
const QUERY = 'query';
const SINK = 'sink';
const SYNCHRONOUS = 'synchronous';
const SSL_KEY = 'ssl_key';
const STREAM = 'stream';
const VERIFY = 'verify';
const TIMEOUT = 'timeout';
const READ_TIMEOUT = 'read_timeout';
const VERSION = 'version';
const FORCE_IP_RESOLVE = 'force_ip_resolve';
Using this command I found that 17,908 constants were used to hardcode URLs, one of the more
frequent use-cases I am trying to address:
cat const.txt | grep -E "https?://" | wc -l
For just a few examples:
From 1stpaygateway-for-woocommerce/includes/class-wc-gateway-1stpay.php
const URL_API = "https://secure.1stpaygateway.net/secure/RestGW/Gateway/Transaction/";
const URL_API_VALIDATION = 'https://secure-v.goemerchant.com/secure/RestGW/Gateway/Transaction/';
const URL_TRANS_CENTER_SUPPORT = 'http://support.goemerchant.com/transaction-center.aspx';
const URL_GATEWAY_OPTIONS_SUPPORT = "http://support.goemerchant.com/transaction-center.aspx?article=gateway-options";
const URL_SUBMIT_CC_BATCH_SUPPORT = "http://support.goemerchant.com/transaction-center.aspx?article=submit-credit-card-batch";
From ad-kangaroo-for-adsense/google_api/Auth/OAuth2.php:
const OAUTH2_REVOKE_URI = 'https://accounts.google.com/o/oauth2/revoke';
const OAUTH2_TOKEN_URI = 'https://accounts.google.com/o/oauth2/token';
const OAUTH2_AUTH_URL = 'https://accounts.google.com/o/oauth2/auth';
I scanned through the const.txt file with less for the non-numeric constants using this
command (of which there were 338,520):
cat const.txt |grep -E -v "=\s*-?\d+(\.\d+)?\s*;" | less
I anecdotally found constants used for the following use-case, many of which are not
"truly" constants and thus could benefit from being able to be initialized in the future
from a configuration source w/o breaking any client code that used said constants:
1. SVG code for icons
2. Regular expressions (3989 uses where the constant contains 'REGEX' in the name)
3. HTML snippets
4. CSS colors
5. XML Schema URLs and/or header elements
6. Error messages
7. User prompts
8. Date/Time formats
9. File extensions
10. Version numbers
11. WordPress Option names
12. Output formats (e.g. 'json', 'xml', 'csv', etc.)
13. Mustache/Handlebar templates
14. HTTP Extension Headers (e.g. "X-*")
15. Browser user-agent signatures
16. Payment processor codes
17. API/Server Error Codes
18. Bitmaps
19. HTTP verbs
20. GUIDs
21. HTML element mapping
22. Currency codes
23. PHP for eval()/code generation
24. UPS/Fedex codes
25. Relative API endpoints
Those are just from "1"-"9" and "a"; there are probably another 10 or
20 categories in "b" thru "z."
Then using this command I calculated that there were 116,548 constants that were integer or float.
cat const.txt | grep -E "=\s*-?\d+(\.\d+)?\s*;" | wc -l
Most of those numeric constants would likely fit in your "truly" constant category, but
anecdotally a significant number would not; I estimate ~5%, or ~5777.
For those that are not "true" constants, a developer might want to allow an end user to
configure them. But because they chose to hardcode as constants initially the plugin can never
evolve their code to allow for configuration without making *breaking* changes.
For just a *few* examples:
const RATIO = 0.5625;
const DEFAULT_FILE_UPLOAD_REQUEST_TIMEOUT = 3600;
const MAX_LINE_LENGTH = 998;
const AUTH_TOKEN_LIFETIME_SECS = 300;
const CACHE_LIFETIME = 86400;
const THUMB_WIDTH = 300;
const NUMBER_COLS_REPEATED_MAX = 1024;
const COMPUTE_PING_CONNECTION_TIMEOUT_S = 0.5;
const DEFAULT_MEMSIZE = 10000;
const BATCH_WRITE_MAX_SIZE = 25;
const FILES_PER_CHUNK=600;
const WRITE_INTERVAL = 10000;
const TIMEOUT_PRECISION = 0.2;
const MIN_DURATION = 0.1;
const MAX_DURATION = 15.0;
const DEFAULT_CONCURRENCY = 5;
Those constants came from these plugins, respectively:
accelerated-mobile-pages/includes/vendor/amp/includes/embeds/class-amp-youtube-embed.php
accesspress-facebook-auto-post/api/facebook-mobile/Facebook/FacebookClient..php
acymailing/front/inc/phpmailer/phpmailer.php
ad-inserter/includes/google-api/Auth/OAuth2.php
add-amazon-block/lib/amazonjs/amazonjs.php
addon-library/inc_php/unitecreator_globals.class.php
advanced-cf7-db/admin/class/PHPExcel/Writer/OpenDocument/Content.php
amazon-s3-and-cloudfront/vendor/Gcp/google/auth/src/Credentials/GCECredentials.php
amazon-s3-and-cloudfront/vendor/Gcp/google/auth/src/Cache/SysVCacheItemPool.php
amazon-s3-uploads/lib/aws-sdk-php/Aws/DynamoDb/Model/BatchRequest/WriteRequestBatchTransfer.php
anybackup/includes/BitsBackupWorker.php
appilder-woocommerce-mobile-app-manager/inc/push-notification/ApnsPHP/Abstract.php
assetsminify/vendor/symfony/process/Symfony/Component/Process/Process.php
auto-post-to-instagram/vendor/mgp25/instagram-php/src/Media/Constraints/DirectConstraints.php
auto-post-to-instagram/vendor/mgp25/instagram-php/src/Media/Constraints/DirectConstraints.php
aws-auto-ses/vendor/aws/aws-sdk-php/src/Multipart/AbstractUploader.php
Note the above examples I give all started with "a." I did not give any examples from
"1" thru "9" and "b" thru "z" etc.:
Anecdotally there was a dompdf package used by many plugins that makes heavy use of constants for
non "truly" constant values, e.g:
From https://github.com/dompdf/dompdf/blob/master/src/FrameDecorator/ListBullet..php
const BULLET_PADDING = 1; // Distance from bullet to text in pt
const BULLET_THICKNESS = 0.04; // Thickness of bullet outline. Screen: 0.08, print: better less,
e.g. 0.04
const BULLET_DESCENT = 0.3; //descent of font below baseline. Todo: Guessed for now.
const BULLET_SIZE = 0.35; // bullet diameter. For now 0.5 of font_size without descent.
I think this analysis proves many PHP developers do use constants in ways you claim you would never.
IOW, the use-cases I am trying to address exist to a significant degree in the wild.
Further I believe I have made the case that there would be significant value in allowing developers
who previously used constants because of prior lack of skill or too-tight timelines to be able to
evolve their code to initialize their constants with code instead of forcing their users to change
their code or worse.
-Mike