Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:123637 X-Original-To: internals@lists.php.net Delivered-To: internals@lists.php.net Received: from php-smtp4.php.net (php-smtp4.php.net [45.112.84.5]) by qa.php.net (Postfix) with ESMTPS id 4EA551A009C for ; Sun, 16 Jun 2024 15:24:42 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1718551553; bh=VNbbhEur45T2a04I0HaorHeeVSIGusnFXtPS4Hiezko=; h=References:In-Reply-To:From:Date:Subject:To:From; b=fD56swZiVqflsQGPJgbxoDfMK7amrm3gdqlDlSi1PojbygNFY/cly7I/dKMQw5oCQ hfn8AtBBoYliNnTissfH5pgQMxVsjVWq4+cdlo5hNDqWQ2Hs3UeTJo391smuQ2gJLr 75sxEXHg+2gPuNPefXM8I8GehQx5ZUh4Z8QPrYaNAnSjvN4Ij7OOAeNQOsGr4bxpOO 3d0kAoC3kuN6btoecRhkTlWFHpSbjL+PRYSu44wYQiZFtYXLtrws652MVv3DCLhk9D 5MkauCMrwTPA33n2+P1NNrbEpGwuHWRE/EqaPC2eb+Ipe1rpu4h6t1GOJL+jTcNjyb 8ERpzvs3zO3YQ== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 480B2180392 for ; Sun, 16 Jun 2024 15:25:52 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.0 (2022-12-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=0.6 required=5.0 tests=BAYES_50,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_PASS,RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H2,SPF_HELO_NONE,SPF_PASS,T_SCC_BODY_TEXT_LINE autolearn=no autolearn_force=no version=4.0.0 X-Spam-Virus: Error (Cannot connect to unix socket '/var/run/clamav/clamd.ctl': connect: Connection refused) X-Envelope-From: Received: from mail-yb1-f179.google.com (mail-yb1-f179.google.com [209.85.219.179]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Sun, 16 Jun 2024 15:25:51 +0000 (UTC) Received: by mail-yb1-f179.google.com with SMTP id 3f1490d57ef6-dfef552d425so4308970276.0 for ; Sun, 16 Jun 2024 08:24:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=dqxtech.net; s=google; t=1718551480; x=1719156280; darn=lists.php.net; h=to:subject:message-id:date:from:in-reply-to:references:mime-version :from:to:cc:subject:date:message-id:reply-to; bh=ZnTM51Mf9OBHNrLhJo4Bd/AAy0nFe0Iae6QxiRSrNw4=; b=aW57aSlMlQ6mkzsG0ffSyx0zEn3mUh1txshbO7FOO7mk00sChw47/LNkcIjh11GKDP kP4wNdb+Akve2fZFvbeHM6NTeICiIWIxsRvFAhSxAIFFjmVJrOdPKTdNG1SM8iA8TdLS /OcwmP+cunb5to4Q95lRSXmaj7tDjt581TozjZFa5XzshUaoeLSnRvAl1/vqC2NMMiB7 z48PMUg0WSF7SXbjfoh7vz2PbVw5NvZCMSfUqEVW53T38upfSzWJWHmUWexUexPR6WIv kqEVcXQ5qxisi4PRWbJwQOa88THgT3/uQwMC2Gr5/JSlVL5cIF7xuqnWE4WEUSWdl2TA Ogkg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1718551480; x=1719156280; h=to:subject:message-id:date:from:in-reply-to:references:mime-version :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=ZnTM51Mf9OBHNrLhJo4Bd/AAy0nFe0Iae6QxiRSrNw4=; b=veGAZyrKJ3wsJQFyNNwz1m9WvnIIHboHEX9/iMyHgb7TcMVYq33shdJcN+eBMp1FMe LAuVhxSx2fJy2jl1hySc1Y2E2aN1bJ/r5zx6mf/nQt8pP9jrid0NNQhtvg8QxfRa2MsF ZvjqPS5Nw8z48/6lfkCA2Vx7qhy+Z+sg96ZOo/YHLS0xkYODA9udiUkcKeHFHFVM9sbn sZ7Lhk8J2L8klpZ2xRa1uiNpdgbcdFZe5gvrD2ZsV8J1xg2FQv+tC0bot0opI6/F7sSU 8ot603csmDERHNVwZePsH5RLSY6vWo0hWamz7mW/8N56uwf32J9SGDXYOZLk/klKWQ6n VVcg== X-Gm-Message-State: AOJu0YwPgHphoW+KXy9jEVFshWLGHlP2rv8Uucw69NqESjEs+KUy6L0N R00nFzxEhSQq0jNdV1QYidBlIF3Tmu0sC/CRZFYK19oESFS/J6O5FyRyVnIIBapVYsnEofiPFqe 5c9NVLL6xTv2mfhcV54ybWdkSjpE5vtOYJiQjig2pzd95dOqZrOJGxQ== X-Google-Smtp-Source: AGHT+IEA7B79NlYnEPYlvmWS4/q+LtsX/tm5bJRA6jyxs4ka+VE02cFOguJVeKJF9C5rWgQRMMKQj6KeRHcCTlql68k= X-Received: by 2002:a25:bfce:0:b0:dfa:c4b8:630e with SMTP id 3f1490d57ef6-dff153b2176mr6908059276.33.1718551479638; Sun, 16 Jun 2024 08:24:39 -0700 (PDT) Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net MIME-Version: 1.0 References: <0cf69a14-f1b5-4077-9d91-d7b579485eec@scriptfusion.com> <936e1aa3-48cc-4552-9f68-676ebcdeb596@rwec.co.uk> <1fc2f2d0-718f-45ec-8968-b66a1bde686e@scriptfusion.com> In-Reply-To: Date: Sun, 16 Jun 2024 17:24:28 +0200 Message-ID: Subject: Re: [PHP-DEV] Static class To: internals@lists.php.net Content-Type: text/plain; charset="UTF-8" From: andreas@dqxtech.net (Andreas Hennings) Regarding function autoloading: A more interesting question to me is a convention where to put each function. With classes, PSR-4 tells us exactly what one can expect to find in a class file with a given name. Perhaps a separate directory tree per package, with one file per sub-namespace? Or just a package-wide functions.php? Regarding static methods vs functions. From the older thread, I can see different levels of possible objection to all-static classes: 1. "Every static method should instead be either a non-static method, or a regular procedural function." 2. "Static methods are acceptable, but no class should exist that has only static methods." 3. "Static methods and all-static classes are acceptable, but no new language feature is needed to specifically support all-static classes." 4. "A new language feature for all-static classes could be acceptable, but it should be very minimal and declarative." I see myself somewhere between 3 and 4. The DX of static methods can be evaluated from two perspectives: The calling side, and the declaration side. On the calling code side, the class of a static method call is usually visible in the place where the call is made, For a function call, the namespace is usually only visible in the imports list at the top of the file. The class name that is part of a static method call hints at an object type, whereas for a namespace fragment it may not be really clear what it describes. In a static factory call, like `Color::fromRgb($red, $green, $blue)`, I would argue that having the class name as part of the call improves DX. On the declaration side, I like to have a static factory in the same place as the class itself. For other cases, it can vary whether having a class name as part of the call improves DX or not. On the declaration side, static methods do bring some additional features, that is, to split out some parts into private and protected methods. This said, in many cases such classes could be converted to non-static instantiable classes. One benefit of static methods is how they can be part of incremental refactoring. - Split some functionality into a private method. - Notice that this method does not depend on any object state, so we make it static. - Now that it is static, it could as well be public, if we clean up the signature a bit. This way it can be reused and tested independently. - Now that it is public static, I rather want to move it out of the class, into a utility class. - Now that we have this standalone method, we could convert it to a procedural function. - OR, we may decide that the entire utility class should actually be converted to a service, and the method to be not static. Or: - Move some functionality out of the constructor into a static factory. - Let the static factory return different implementations based on the parameters. E.g. Color::fromRbg() could return a GrayscaleColor object or a RainbowColor object, depending on parameter values. - Make `Color` an all-static class, because the specific implementations have dedicated classes. - Convert the static methods on `Color` into object methods on `ColorFromRgbFactory`. - OR, we discover static polymorphism (*) and "abstract protected static function". - Later we might decide that this was awkward, and convert everything to object methods. In both of these scenarios we have steps with an all-static class. There are possibilities to move away from that. But in some cases the all-static class is actually the sweet spot where we want to stay. Another case for an all-static class would be a class that only contains class constants. Yes, we can use `define(...)`, but sometimes we want class constants, for similar arguments as above. A third case is if the all-static class already exists, as part of a legacy codebase, and we could not break it up even if we wanted to. If we do end up with an all-static class, even as an intermediate state, it can be useful to declare this in some way. Developers should immediately see whether a class is instantiable or not. A trait or a base class or an explicit private constructor can do the job, but it won't be as universal as a language feature. On the other hand: From the older proposal by Lanre Waju: > All methods and variables in the class are implicitly static. I would very much oppose this. These implicit semantics would make it harder and error-prone to move methods around from a regular class to an all-static class. And it would be confusing to look at such a method, because typically the class-level static declaration won't be visible when you look at the method. Also think of the noise in git commits caused by the conversion to an all-static class. (Btw the same problem applies to class-level readonly) To me, the best version of a class-level "static" keyword would be as a contract, and nothing more: - The class cannot be instantiated. - It cannot have a constructor or destructor, or any magic methods that only apply to objects. - It can only extend or be extended by other static classes. (otherwise it should be declared "abstract" instead) The same could be achieved with a native base class or trait with private constructor, or even the same thing from php-fig. This would be less clear and less powerful than a static keyword, but also less invasive to the language. (One such language feature could be a language-level base class or trait, simply for the sake of unification) From this thread: > In particular: static classes are implied final and they cannot inherit from any other class. With the version of the static keyword as above, I would be against this limitation. Static polymorphism (*) is possible today, and it should be perfectly fine to slap a "static" keyword on these classes, if they are all-static. This is not about whether or not we like or dislike static polymorphism, but about not attaching unrelated meaning to a keyword. An explicit "static final class" is more obvious than "static class" with implicit final. (*) The term "static polymorphism" is a bit questionable. I obviously mean this: class B { static function f() { return static::g(); } protected static function g() { return null; } } class C extends B { protected static function g() { return 5; } } assert(C::f() === 5); I have used this in the past, and I am now questioning my choices in some of these cases. Still I would not consider it an anti-pattern. --- Andreas