Table of Contents
1. 前言
2. 项目代码结构
3. 容器完整代码
3.1 容器主要提供方法
3.2 符合PSR-11标准
3.3 容器的基本存储
3.4 自动依赖解决
3.4.1 解决类构造函数依赖
3.4.2 解决 callable 的参数依赖
4. 未完..不一定续
Home Backend Development PHP Tutorial Implement PHP's automatic dependency injection container EasyDI container

Implement PHP's automatic dependency injection container EasyDI container

Apr 28, 2018 am 10:37 AM
php rely injection

这篇文章主要介绍了关于实现PHP的自动依赖注入容器 EasyDI容器,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下


  • 1. 前言

  • 2. 项目代码结构

  • 3. 容器完整代码

    • 3.4.1 解决类构造函数依赖

    • 3.4.2 解决 callable 的参数依赖

    • 3.1 容器主要提供方法

    • 3.2 符合PSR-11标准

    • 3.3 容器的基本存储

    • 3.4 自动依赖解决

  • 4. 未完..不一定续


1. 前言

在看了一些容器实现代码后, 就手痒想要自己实现一个, 因此也就有了本文接下来的内容.

首先, 实现的容器需要具有以下几点特性:

  • 符合PSR-11标准

  • 实现基本的容器存储功能

  • 具有自动依赖解决能力

本项目代码由GitHub托管

可使用Composer进行安装 composer require yjx/easy-di

2. 项目代码结构

|-src
    |-Exception
        |-InstantiateException.php (实现Psr\Container\ContainerExceptionInterface)
        |-InvalidArgumentException.php (实现Psr\Container\ContainerExceptionInterface)
        |-UnknownIdentifierException.php (实现Psr\Container\NotFoundExceptionInterface)
    |-Container.php # 容器|-tests
    |-UnitTest
        |-ContainerTest.php
Copy after login

3. 容器完整代码

代码版本 v1.0.1

<?php
namespace EasyDI;
use EasyDI\Exception\UnknownIdentifierException;
use EasyDI\Exception\InvalidArgumentException;
use EasyDI\Exception\InstantiateException;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;use Psr\Container\NotFoundExceptionInterface;
class Container implements ContainerInterface{
    /**
     * 保存 参数, 已实例化的对象
     * @var array
     */
    private $instance = [];    private $shared = [];    private $raw = [];    private $params = [];    /**
     * 保存 定义的 工厂等
     * @var array
     */
    private $binding = [];    public function __construct()
    {
        $this->raw(ContainerInterface::class, $this);
        $this->raw(self::class, $this);
    }


    /**
     * Finds an entry of the container by its identifier and returns it.
     *
     * @param string $id Identifier of the entry to look for.
     *
     * @throws NotFoundExceptionInterface  No entry was found for **this** identifier.
     * @throws ContainerExceptionInterface Error while retrieving the entry.
     *
     * @return mixed Entry.
     */    public function get($id, $parameters = [], $shared=false)
    {
        if (!$this->has($id)) {            throw new UnknownIdentifierException($id);
        }        
        if (array_key_exists($id, $this->raw)) {            
        return $this->raw[$id];
        }        
        if (array_key_exists($id, $this->instance)) {            
        return $this->instance[$id];
        }        
        $define = array_key_exists($id, $this->binding) ? $this->binding[$id] : $id;        
        if ($define instanceof \Closure) {            
        $instance = $this->call($define, $parameters);
        } else {            
        // string
            $class = $define;            
            $params = (empty($this->params[$id]) ? [] : $this->params[$id]) + $parameters;            
            // Case: "\\xxx\\xxx"=>"abc"
            if ($id !== $class && $this->has($class)) {                
            $instance = $this->get($class, $params);
            } else {                
            $dependencies = $this->getClassDependencies($class, $params);                
            if (is_null($dependencies) || empty($dependencies)) {                    
            $instance = $this->getReflectionClass($class)->newInstanceWithoutConstructor();
                } else {                    
                $instance = $this->getReflectionClass($class)->newInstanceArgs($dependencies);
                }
            }
        }        if ($shared || (isset($this->shared[$id]) && $this->shared[$id])) {            
        $this->instance[$id] = $instance;
        }        return $instance;
    }    /**
     * @param callback $function
     * @param array $parameters
     * @return mixed
     * @throws InvalidArgumentException 传入错误的参数
     * @throws InstantiateException
     */
    public function call($function, $parameters=[], $shared=false)
    {
        //参考 http://php.net/manual/zh/function.call-user-func-array.php#121292 实现解析$function

        $class = null;        $method = null;        $object = null;        // Case1: function() {}
        if ($function instanceof \Closure) {            $method = $function;
        } elseif (is_array($function) && count($function)==2) {            // Case2: [$object, $methodName]
            if (is_object($function[0])) {                $object = $function[0];                $class = get_class($object);
            } elseif (is_string($function[0])) {                // Case3: [$className, $staticMethodName]
                $class = $function[0];
            }            if (is_string($function[1])) {                $method = $function[1];
            }
        } elseif (is_string($function) && strpos($function, &#39;::&#39;) !== false) {            // Case4: "class::staticMethod"
            list($class, $method) = explode(&#39;::&#39;, $function);
        } elseif (is_scalar($function)) {            // Case5: "functionName"
            $method = $function;
        } else {            throw new InvalidArgumentException("Case not allowed! Invalid Data supplied!");
        }        try {            if (!is_null($class) && !is_null($method)) {                $reflectionFunc = $this->getReflectionMethod($class, $method);
            } elseif (!is_null($method)) {                $reflectionFunc = $this->getReflectionFunction($method);
            } else {                throw new InvalidArgumentException("class:$class method:$method");
            }
        } catch (\ReflectionException $e) {//            var_dump($e->getTraceAsString());
            throw new InvalidArgumentException("class:$class method:$method", 0, $e);
        }        
        $parameters = $this->getFuncDependencies($reflectionFunc, $parameters);        
        if ($reflectionFunc instanceof \ReflectionFunction) {            
        return $reflectionFunc->invokeArgs($parameters);
        } elseif ($reflectionFunc->isStatic()) {            
        return $reflectionFunc->invokeArgs(null, $parameters);
        } elseif (!empty($object)) {            
        return $reflectionFunc->invokeArgs($object, $parameters);
        } elseif (!is_null($class) && $this->has($class)) {            
        $object = $this->get($class, [], $shared);            
        return $reflectionFunc->invokeArgs($object, $parameters);
        }        
        throw new InvalidArgumentException("class:$class method:$method, unable to invoke.");
    }    /**
     * @param $class
     * @param array $parameters
     * @throws \ReflectionException
     */
    protected function getClassDependencies($class, $parameters=[])
    {
        // 获取类的反射类
        $reflectionClass = $this->getReflectionClass($class);        
        if (!$reflectionClass->isInstantiable()) {            
        throw new InstantiateException($class);
        }        // 获取构造函数反射类
        $reflectionMethod = $reflectionClass->getConstructor();        
        if (is_null($reflectionMethod)) {            
        return null;
        }        
        return $this->getFuncDependencies($reflectionMethod, $parameters, $class);
    }    
    protected function getFuncDependencies(\ReflectionFunctionAbstract $reflectionFunc, $parameters=[], $class="")
    {
        $params = [];        // 获取构造函数参数的反射类
        $reflectionParameterArr = $reflectionFunc->getParameters();        
        foreach ($reflectionParameterArr as $reflectionParameter) {            
        $paramName = $reflectionParameter->getName();            
        $paramPos = $reflectionParameter->getPosition();            
        $paramClass = $reflectionParameter->getClass();            
        $context = [&#39;pos&#39;=>$paramPos, &#39;name&#39;=>$paramName, &#39;class&#39;=>$paramClass, &#39;from_class&#39;=>$class];            
        // 优先考虑 $parameters
            if (isset($parameters[$paramName]) || isset($parameters[$paramPos])) {                
            $tmpParam = isset($parameters[$paramName]) ? $parameters[$paramName] : $parameters[$paramPos];                
            if (gettype($tmpParam) == &#39;object&#39; && !is_a($tmpParam, $paramClass->getName())) {                    
            throw new InstantiateException($class."::".$reflectionFunc->getName(), $parameters + [&#39;__context&#39;=>$context, &#39;tmpParam&#39;=>get_class($tmpParam)]);
                }                
                $params[] = $tmpParam;//                
                $params[] = isset($parameters[$paramName]) ? $parameters[$paramName] : $parameters[$pos];
            } elseif (empty($paramClass)) {            
            // 若参数不是class类型

                // 优先使用默认值, 只能用于判断用户定义的函数/方法, 对系统定义的函数/方法无效, 也同样无法获取默认值
                if ($reflectionParameter->isDefaultValueAvailable()) {                    $params[] = $reflectionParameter->getDefaultValue();
                } elseif ($reflectionFunc->isUserDefined()) {                    
                throw new InstantiateException("UserDefined. ".$class."::".$reflectionFunc->getName());
                } elseif ($reflectionParameter->isOptional()) {                    break;
                } else {                    throw new InstantiateException("SystemDefined.  ".$class."::".$reflectionFunc->getName());
                }
            } else {            // 参数是类类型, 优先考虑解析
                if ($this->has($paramClass->getName())) {                    $params[] = $this->get($paramClass->getName());
                } elseif ($reflectionParameter->allowsNull()) {                    $params[] = null;
                } else {                    throw new InstantiateException($class."::".$reflectionFunc->getName()."  {$paramClass->getName()} ");
                }
            }
        }        return $params;
    }    protected function getReflectionClass($class, $ignoreException=false)
    {
        static $cache = [];        if (array_key_exists($class, $cache)) {            return $cache[$class];
        }        try {            $reflectionClass = new \ReflectionClass($class);
        } catch (\Exception $e) {            if (!$ignoreException) {                throw new InstantiateException($class, 0, $e);
            }            $reflectionClass = null;
        }        return $cache[$class] = $reflectionClass;
    }    protected function getReflectionMethod($class, $name)
    {
        static $cache = [];        if (is_object($class)) {            $class = get_class($class);
        }        if (array_key_exists($class, $cache) && array_key_exists($name, $cache[$class])) {            return $cache[$class][$name];
        }        $reflectionFunc = new \ReflectionMethod($class, $name);        return $cache[$class][$name] = $reflectionFunc;
    }    protected function getReflectionFunction($name)
    {
        static $closureCache;        static $cache = [];        $isClosure = is_object($name) && $name instanceof \Closure;        $isString = is_string($name);        if (!$isString && !$isClosure) {            throw new InvalidArgumentException("$name can&#39;t get reflection func.");
        }        if ($isString && array_key_exists($name, $cache)) {            return $cache[$name];
        }        if ($isClosure) {            if (is_null($closureCache)) {                $closureCache = new \SplObjectStorage();
            }            if ($closureCache->contains($name)) {                return $closureCache[$name];
            }
        }        $reflectionFunc = new \ReflectionFunction($name);        if ($isString) {            $cache[$name] = $reflectionFunc;
        }        if ($isClosure) {            $closureCache->attach($name, $reflectionFunc);
        }        return $reflectionFunc;
    }    /**
     * Returns true if the container can return an entry for the given identifier.
     * Returns false otherwise.
     *
     * `has($id)` returning true does not mean that `get($id)` will not throw an exception.
     * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
     *
     * @param string $id Identifier of the entry to look for.
     *
     * @return bool
     */
    public function has($id)
    {
        $has = array_key_exists($id, $this->binding) || array_key_exists($id, $this->raw) || array_key_exists($id, $this->instance);        
        if (!$has) {            $reflectionClass = $this->getReflectionClass($id, true);            
        if (!empty($reflectionClass)) {                $has = true;
            }
        }        return $has;
    }    public function needResolve($id)
    {
        return !(array_key_exists($id, $this->raw) && (array_key_exists($id, $this->instance) && $this->shared[$id]));
    }    public function keys()
    {
        return array_unique(array_merge(array_keys($this->raw), array_keys($this->binding), array_keys($this->instance)));
    }    public function instanceKeys()
    {
        return array_unique(array_keys($this->instance));
    }    public function unset($id)
    {
        unset($this->shared[$id], $this->binding[$id], $this->raw[$id], $this->instance[$id], $this->params[$id]);
    }    public function singleton($id, $value, $params=[])
    {
        $this->set($id, $value, $params, true);
    }    /**
     * 想好定义数组, 和定义普通项
     * @param $id
     * @param $value
     * @param bool $shared
     */
    public function set($id, $value, $params=[], $shared=false)
    {
        if (is_object($value) && !($value instanceof  \Closure)) {            $this->raw($id, $value);            return;
        } elseif ($value instanceof \Closure) {            // no content
        } elseif (is_array($value)) {            $value = [                &#39;class&#39; => $id,                &#39;params&#39; => [],                &#39;shared&#39; => $shared
                ] + $value;            if (!isset($value[&#39;class&#39;])) {                $value[&#39;class&#39;] = $id;
            }            $params = $value[&#39;params&#39;] + $params;            $shared = $value[&#39;shared&#39;];            $value = $value[&#39;class&#39;];
        } elseif (is_string($value)) {            // no content
        }        $this->binding[$id] = $value;        $this->shared[$id] = $shared;        $this->params[$id] = $params;
    }    public function raw($id, $value)
    {
        $this->unset($id);        $this->raw[$id] = $value;
    }    public function batchRaw(array $data)
    {
        foreach ($data as $key=>$value) {            $this->raw($key, $value);
        }
    }    public function batchSet(array $data, $shared=false)
    {
        foreach ($data as $key=>$value) {            $this->set($key, $value, $shared);
        }
    }

}
Copy after login

3.1 容器主要提供方法

容器提供方法:
- raw(string $id, mixed $value)
适用于保存参数, $value可以是任何类型, 容器不会对其进行解析.

  • set(string $id, \Closure|array|string $value, array $params=[], bool $shared=false)
    定义服务

  • singleton(string $id, \Closure|array|string $value, array $params=[])
    等同调用set($id, $value, $params, true)

  • has(string $id)
    判断容器是否包含$id对应条目

  • get(string $id, array $params = [])
    从容器中获取

    params可优先参与到条目实例化过程中的依赖注入


  • call(callable $function, array $params=[])
    利用容器来调用callable, 由容器自动注入依赖.

  • unset(string $id)
    从容器中移除$id对应条目

3.2 符合PSR-11标准

EasyDI(本容器)实现了 Psr\Container\ContainerInterface 接口, 提供 has($id)get($id, $params=[]) 两个方法用于判断及获取条目.

对于无法解析的条目识别符, 则会抛出异常(实现了 NotFoundExceptionInterface 接口).

3.3 容器的基本存储

容器可用于保存 不被解析的条目, 及自动解析的条目.

  • 不被解析的条目
    主要用于保存 配置参数, 已实例化对象, 不被解析的闭包

  • 自动解析的条目
    get(...) 时会被容器自动解析, 若是 闭包 则会自动调用, 若是 类名 则会实例化, 若是 别名 则会解析其对应的条目.

3.4 自动依赖解决

EasyDI 在调用 闭包 及 实例化 已经 调用函数/方法(call()) 时能够自动注入所需的依赖, 其中实现的原理是使用了PHP自带的反射API.

此处主要用到的反射API如下:

  • ReflectionClass

  • ReflectionFunction

  • ReflectionMethod

  • ReflectionParameter

3.4.1 解决类构造函数依赖

解析的一般步骤:

  1. 获取类的反射类 $reflectionClass = new ReflectionClass($className)

  2. 判断能够实例化 $reflectionClass->isInstantiable()

  3. 若能实例化, 则获取对应的构造函数的反射方法类 $reflectionMethod = $reflectionClass->getConstructor()
    3.1. 若返回null, 则表示无构造函数可直接跳到步骤6
    3.2 若返回ReflectionMethod实例, 则开始解析其参数

  4. 获取构造函数所需的所有依赖参数类 $reflectionParameters = $reflectionMethod->getParameters

  5. 逐个解析依赖参数 $reflectionParameter
    5.1 获取参数对应名及位置 $reflectionParameter->getName(), $reflectionParameter->getClass()
    5.2 获取参数对应类型 $paramClass = $reflectionParameter->getClass()
    5.2.1 若本次解析手动注入了依赖参数, 则根据参数位置及参数名直接使用传入的依赖参数 Eg. $container->get($xx, [1=>123, &#39;e&#39;=>new \Exception()])
    5.2.2 若参数是标量类型, 若参数有默认值($reflectionParameter->isDefaultValueAvailable())则使用默认值, 否则抛出异常(无法处理该依赖)
    5.2.3 若参数是 class 类型, 若容器可解析该类型, 则由容器自动实例化 $this->get($paramClass->getName()), 若无法解析但该参数允许null, 则传入null值, 否则抛出异常(无法处理来依赖)

  6. 若依赖参数为空则调用 $reflectionClass->newInstanceWithoutConstructor(), 否则调用 $reflectionClass->newInstanceArgs($dependencies); //$dependencies为步骤5中构造的依赖参数数组

具体完整代码请参照容器类的 getClassDependencies(...) 方法.


3.4.2 解决 callable 的参数依赖

使用 call(...) 来调用 可调用 时, 自动解决依赖同样类似上述过程, 只是需要区分是 类函数, 类静态方法 还是 普通方法, 并相应的使用不同的反射类来解析,

具体完整代码请参照容器类的 call(...) 方法

class UserManager{
    private $mailer;    public function __construct(Mailer $mailer)
    {
        $this->mailer = $mailer;
    }    public function register($email, $password)
    {
        // The user just registered, we create his account
        // ...

        // We send him an email to say hello!
        $this->mailer->mail($email, &#39;Hello and welcome!&#39;);
    }    public function quickSend(Mailer $mailer, $email, $password)
    {
        $mailer->mail($email, &#39;Hello and welcome!&#39;);
    }
}function testFunc(UserManager $manager){
    return "test";
}// 实例化容器$c = new EasyDI\Container();// 输出: &#39;test&#39;echo $c->call(&#39;testFunc&#39;)."\n";    

// 输出: &#39;test&#39;echo $c->call(function (UserManager $tmp) {
    return &#39;test&#39;;
}); 

// 自动实例化UserManager对象   [$className, $methodName]$c->call([UserManager::class, &#39;register&#39;], [&#39;password&#39;=>123, &#39;email&#39;=>&#39;1@1.1&#39;]);   

// 自动实例化UserManager对象   $methodFullName$c->call(UserManager::class.&#39;::&#39;.&#39;register&#39;, [&#39;password&#39;=>123, &#39;email&#39;=>&#39;1@1.1&#39;]); 

// 调用类的静态方法 [$className, $staticMethodName]
$c->call([UserManager::class, &#39;quickSend&#39;], [&#39;password&#39;=>123, &#39;email&#39;=>&#39;1@1.1&#39;]);  

// 使用字符串调用类的静态方法 $staticMethodFullName$c->call(UserManager::class.&#39;::&#39;.&#39;quickSend&#39;, [&#39;password&#39;=>123, &#39;email&#39;=>&#39;1@1.1&#39;]);    

// [$obj, $methodName] 
$c->call([new UserManager(new Mailer()), &#39;register&#39;], [&#39;password&#39;=>123, &#39;email&#39;=>&#39;1@1.1&#39;]);    

// [$obj, $staticMethodName]
$c->call([new UserManager(new Mailer()), &#39;quickSend&#39;], [&#39;password&#39;=>123, &#39;email&#39;=>&#39;1@1.1&#39;]);
Copy after login

4. 未完..不一定续

暂时写到此处.

后续项目最新代码直接在 GitHub 上维护, 该博文后续视评论需求来决定是否补充.

相关推荐:

几行代码轻松实现PHP文件打包下载zip

predis如何实现phpredis的pconnect方法

The above is the detailed content of Implement PHP's automatic dependency injection container EasyDI container. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Roblox: Bubble Gum Simulator Infinity - How To Get And Use Royal Keys
3 weeks ago By 尊渡假赌尊渡假赌尊渡假赌
Mandragora: Whispers Of The Witch Tree - How To Unlock The Grappling Hook
3 weeks ago By 尊渡假赌尊渡假赌尊渡假赌
Nordhold: Fusion System, Explained
3 weeks ago By 尊渡假赌尊渡假赌尊渡假赌

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

Hot Topics

Java Tutorial
1668
14
PHP Tutorial
1273
29
C# Tutorial
1256
24
PHP: A Key Language for Web Development PHP: A Key Language for Web Development Apr 13, 2025 am 12:08 AM

PHP is a scripting language widely used on the server side, especially suitable for web development. 1.PHP can embed HTML, process HTTP requests and responses, and supports a variety of databases. 2.PHP is used to generate dynamic web content, process form data, access databases, etc., with strong community support and open source resources. 3. PHP is an interpreted language, and the execution process includes lexical analysis, grammatical analysis, compilation and execution. 4.PHP can be combined with MySQL for advanced applications such as user registration systems. 5. When debugging PHP, you can use functions such as error_reporting() and var_dump(). 6. Optimize PHP code to use caching mechanisms, optimize database queries and use built-in functions. 7

PHP vs. Python: Understanding the Differences PHP vs. Python: Understanding the Differences Apr 11, 2025 am 12:15 AM

PHP and Python each have their own advantages, and the choice should be based on project requirements. 1.PHP is suitable for web development, with simple syntax and high execution efficiency. 2. Python is suitable for data science and machine learning, with concise syntax and rich libraries.

PHP and Python: Comparing Two Popular Programming Languages PHP and Python: Comparing Two Popular Programming Languages Apr 14, 2025 am 12:13 AM

PHP and Python each have their own advantages, and choose according to project requirements. 1.PHP is suitable for web development, especially for rapid development and maintenance of websites. 2. Python is suitable for data science, machine learning and artificial intelligence, with concise syntax and suitable for beginners.

PHP in Action: Real-World Examples and Applications PHP in Action: Real-World Examples and Applications Apr 14, 2025 am 12:19 AM

PHP is widely used in e-commerce, content management systems and API development. 1) E-commerce: used for shopping cart function and payment processing. 2) Content management system: used for dynamic content generation and user management. 3) API development: used for RESTful API development and API security. Through performance optimization and best practices, the efficiency and maintainability of PHP applications are improved.

The Enduring Relevance of PHP: Is It Still Alive? The Enduring Relevance of PHP: Is It Still Alive? Apr 14, 2025 am 12:12 AM

PHP is still dynamic and still occupies an important position in the field of modern programming. 1) PHP's simplicity and powerful community support make it widely used in web development; 2) Its flexibility and stability make it outstanding in handling web forms, database operations and file processing; 3) PHP is constantly evolving and optimizing, suitable for beginners and experienced developers.

PHP and Python: Different Paradigms Explained PHP and Python: Different Paradigms Explained Apr 18, 2025 am 12:26 AM

PHP is mainly procedural programming, but also supports object-oriented programming (OOP); Python supports a variety of paradigms, including OOP, functional and procedural programming. PHP is suitable for web development, and Python is suitable for a variety of applications such as data analysis and machine learning.

PHP vs. Other Languages: A Comparison PHP vs. Other Languages: A Comparison Apr 13, 2025 am 12:19 AM

PHP is suitable for web development, especially in rapid development and processing dynamic content, but is not good at data science and enterprise-level applications. Compared with Python, PHP has more advantages in web development, but is not as good as Python in the field of data science; compared with Java, PHP performs worse in enterprise-level applications, but is more flexible in web development; compared with JavaScript, PHP is more concise in back-end development, but is not as good as JavaScript in front-end development.

PHP and Python: Code Examples and Comparison PHP and Python: Code Examples and Comparison Apr 15, 2025 am 12:07 AM

PHP and Python have their own advantages and disadvantages, and the choice depends on project needs and personal preferences. 1.PHP is suitable for rapid development and maintenance of large-scale web applications. 2. Python dominates the field of data science and machine learning.

See all articles