Php依赖注入容器Pimple的笔记

话说许久没有写技术类的笔记了,也该写点东西了,距离上次说 container 已经过去4个月了,期间也一直在学习,但是不知道写点什么好,说实话还是很怀念 14 年下半年,那半年的进步真的很大,那时候自己愿意看东西,也愿意写东西,也许写东西能让自己进步更快吧,所以17年了也应该继续进步了,否则问题大大的啊,下班的时候跟小伙伴聊天,做技术的就应该一直学习,否则就会被拉下很远很远。好了,说了这么多废话,说点正文吧,其实,想写 laravel 的 container 的笔记的,可是,自己看了许久,也没有很好的理解,理解到的部分也是很片面的东西,不过 laravel 的 container 真的很强大,要比今天写的这个强大的多,不过强大的带来的问题,就是可能会慢,毕竟用到了反射,今天这个 Pimple 则相对简单的多了,没有那么多复杂的东西,用起来也很顺手而且比较容易理解,所以今天就用这个做笔记了。

原本想分段做这个的笔记的,后来觉得应该吧知识点拆分开说明,然后再用整体代码在里面做注释的方式来解释这段更好一些,于是乎,准备开整,btw,过几天准备更新一下编辑器,现在这个自己搞得编辑器还是有些简陋的。好吧,进入正文了。

class Container implements \ArrayAccess

这个类实现了 ArrayAccess 接口,ArrayAccess 是个什么东西了,大家首先看一下 PHP 官网的手册 http://php.net/manual/zh/class.arrayaccess.php 好吧,我就简单说一句,就是让对象能够像数组一样操作了。具体需要实现的几个接口,大家看一下手册吧,我觉得我的说明肯定没有手册写得好,我更多理解的东西,会在代码注释中写一下。

public function __construct(array $values = array())
    {
        $this->factories = new \SplObjectStorage();
        $this->protected = new \SplObjectStorage();

        foreach ($values as $key => $value) {
            $this->offsetSet($key, $value);
        }
    }

在构造方法中又出现了 SplObjectStorage 类,那么这个 SplObjectStorage 是个什么东西呢,我们再去看一下手册 http://php.net/manual/zh/class.splobjectstorage.php 这个在手册中可惜翻译的不完全(根本没有翻译),所以我就简单的说说,这个类其实是实现了 Countable , Iterator , Serializable , ArrayAccess 这4个接口,这4个接口大家也可以看一下手册,看完了就知道这个类是干什么的了,我再来拆分说下 Countable 就是让一个类可以用一个计数器, Iterator 就是可以用 foreach 这些去循环,Serializable 就是可以序列化,ArrayAccess 上面说过了就不多说了。其实刚开始很难理解这个类,不过多看看手册,别人的示例代码,自己在多写一些可能就了解了。这个如何理解我也说不太好。大家见谅。

好吧,我觉得是额外知识点的就上面那些,等多的笔记我将在下面的代码注释中说明白,大家可以看看,顺便帮忙指正。

<?php

/*
 * This file is part of Pimple.
 *
 * Copyright (c) 2009 Fabien Potencier
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is furnished
 * to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

namespace Pimple;

/**
 * Container main class.
 *
 * @author  Fabien Potencier
 */
class Container implements \ArrayAccess
{
    private $values = array(); // 存储 value 的数组
    private $factories; // 存储工厂方法的对象
    private $protected; // 存储保护方法的对象
    private $frozen = array(); // 存储冻结的数组,也就是在这个数组里面的 key 的 value 是不可更改的了
    private $raw = array(); // 存储
    private $keys = array(); // 存储 key 的数组

    /**
     * Instantiate the container.
     *
     * Objects and parameters can be passed as argument to the constructor.
     *
     * @param array $values The parameters or objects.
     */
    public function __construct(array $values = array())
    {
        // 构造工厂对象以及保护方法对象
        $this->factories = new \SplObjectStorage();
        $this->protected = new \SplObjectStorage();
        // 把初始化的值存放到现有的里面
        foreach ($values as $key => $value) {
            $this->offsetSet($key, $value);
        }
    }

    /**
     * Sets a parameter or an object.
     *
     * Objects must be defined as Closures.
     *
     * Allowing any PHP callable leads to difficult to debug problems
     * as function names (strings) are callable (creating a function with
     * the same name as an existing parameter would break your container).
     *
     * @param string $id    The unique identifier for the parameter or object
     * @param mixed  $value The value of the parameter or a closure to define an object
     *
     * @throws \RuntimeException Prevent override of a frozen service
     * 设置相关值以及对象
     */
    public function offsetSet($id, $value)
    {
        // 如果这个值被 frozen 了,就不允许更改了
        // 应该是为了保持高效性,会把 get 过的值存储起来,所以就不在调用了也就不允许更改了
        // 其实也可以更改,在下面的方法中说明
        if (isset($this->frozen[$id])) {
            throw new \RuntimeException(sprintf('Cannot override frozen service "%s".', $id));
        }
        // 存储值方法
        $this->values[$id] = $value;
        // 存储 key 的值
        $this->keys[$id] = true;
    }

    /**
     * Gets a parameter or an object.
     *
     * @param string $id The unique identifier for the parameter or object
     *
     * @return mixed The value of the parameter or an object
     *
     * @throws \InvalidArgumentException if the identifier is not defined
     * 获取值得方法
     */
    public function offsetGet($id)
    {
        // 如果没有设置 key 则抛出异常
        if (!isset($this->keys[$id])) {
            throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
        }

        // 如果 raw 里面已经有了, 或者 values 里面存储对应 key 的值不是个 obj,或者 protected 里面也有对应的值 或者值里面的方法存在,则直接返回,values 数组里面的结果
        if (
            isset($this->raw[$id])
            || !is_object($this->values[$id])
            || isset($this->protected[$this->values[$id]])
            || !method_exists($this->values[$id], '__invoke')
        ) {
            return $this->values[$id];
        }

        // 如果工厂方法里面设置了相关方法则要直接返回
        if (isset($this->factories[$this->values[$id]])) {
            return $this->values[$id]($this);
        }

        // 获取值里面的方法
        $raw = $this->values[$id];
        // 执行上面获取到的方法获取返回值 并且覆盖 values
        $val = $this->values[$id] = $raw($this);
        // 把原始方法存储到 raw 数组里面,用来给 raw 方法调用
        $this->raw[$id] = $raw;
        // 把这个值设置为冻结,不允许将来的更改
        $this->frozen[$id] = true;
        // 返回结果值
        return $val;
    }

    /**
     * Checks if a parameter or an object is set.
     *
     * @param string $id The unique identifier for the parameter or object
     *
     * @return bool
     * 获取 key 是否存在
     */
    public function offsetExists($id)
    {
        return isset($this->keys[$id]);
    }

    /**
     * Unsets a parameter or an object.
     *
     * @param string $id The unique identifier for the parameter or object
     * 删除掉 key
     */
    public function offsetUnset($id)
    {
        // 如果存在则删除相关的值
        if (isset($this->keys[$id])) {
            // 如果存储的是个对象,则删除相关的值
            if (is_object($this->values[$id])) {
                unset($this->factories[$this->values[$id]], $this->protected[$this->values[$id]]);
            }
            // 删除普通数组里面的值
            unset($this->values[$id], $this->frozen[$id], $this->raw[$id], $this->keys[$id]);
        }
    }

    /**
     * Marks a callable as being a factory service.
     *
     * @param callable $callable A service definition to be used as a factory
     *
     * @return callable The passed callable
     *
     * @throws \InvalidArgumentException Service definition has to be a closure of an invokable object
     * 设置工厂方法
     */
    public function factory($callable)
    {
        if (!method_exists($callable, '__invoke')) {
            throw new \InvalidArgumentException('Service definition is not a Closure or invokable object.');
        }

        $this->factories->attach($callable);

        return $callable;
    }

    /**
     * Protects a callable from being interpreted as a service.
     *
     * This is useful when you want to store a callable as a parameter.
     *
     * @param callable $callable A callable to protect from being evaluated
     *
     * @return callable The passed callable
     *
     * @throws \InvalidArgumentException Service definition has to be a closure of an invokable object
     */
    public function protect($callable)
    {
        if (!method_exists($callable, '__invoke')) {
            throw new \InvalidArgumentException('Callable is not a Closure or invokable object.');
        }

        $this->protected->attach($callable);

        return $callable;
    }

    /**
     * Gets a parameter or the closure defining an object.
     *
     * @param string $id The unique identifier for the parameter or object
     *
     * @return mixed The value of the parameter or the closure defining an object
     *
     * @throws \InvalidArgumentException if the identifier is not defined
     * 其实这个就是获取设置的对象或者方法
     */
    public function raw($id)
    {
        if (!isset($this->keys[$id])) {
            throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
        }
        if (isset($this->raw[$id])) {
            return $this->raw[$id];
        }

        return $this->values[$id];
    }

    /**
     * Extends an object definition.
     *
     * Useful when you want to extend an existing object definition,
     * without necessarily loading that object.
     *
     * @param string   $id       The unique identifier for the object
     * @param callable $callable A service definition to extend the original
     *
     * @return callable The wrapped callable
     *
     * @throws \InvalidArgumentException if the identifier is not defined or not a service definition
     * 修改已经存在的值
     */
    public function extend($id, $callable)
    {
        if (!isset($this->keys[$id])) {
            throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
        }

        if (!is_object($this->values[$id]) || !method_exists($this->values[$id], '__invoke')) {
            throw new \InvalidArgumentException(sprintf('Identifier "%s" does not contain an object definition.', $id));
        }

        if (!is_object($callable) || !method_exists($callable, '__invoke')) {
            throw new \InvalidArgumentException('Extension service definition is not a Closure or invokable object.');
        }

        $factory = $this->values[$id];

        $extended = function ($c) use ($callable, $factory) {
            return $callable($factory($c), $c);
        };

        if (isset($this->factories[$factory])) {
            $this->factories->detach($factory);
            $this->factories->attach($extended);
        }

        return $this[$id] = $extended;
    }

    /**
     * Returns all defined value names.
     *
     * @return array An array of value names
     * 获取所有的 key
     */
    public function keys()
    {
        return array_keys($this->values);
    }

    /**
     * Registers a service provider.
     *
     * @param ServiceProviderInterface $provider A ServiceProviderInterface instance
     * @param array                    $values   An array of values that customizes the provider
     *
     * @return static
     * 注册自己的服务用的,后续文章体现
     */
    public function register(ServiceProviderInterface $provider, array $values = array())
    {
        $provider->register($this);

        foreach ($values as $key => $value) {
            $this[$key] = $value;
        }

        return $this;
    }
}

好吧,上面注释的都已经写出来了,个人觉得很简单,但是有几个方法我觉得可以扩展开来说,可能在过几天的文章里面展开来说说,基本上就是 register、extend和 raw 这几个方法的具体说明了。

哦对了,开始我觉得 factory 和 protect 方法差不多,其实还是有区别的,就是 factory 会有一个 $container 的参数,protect 方法就是存储的普通方法,区别就这么简单了。不过更多的方法,也会在后续文章说明的。

从权限控制到合理分解系统的思考

本来这篇文章想写一个不利用轮子在laravel上实现rbac的东西的,其实也就是自己造一个rbac的轮子。

但是,最后自己还是懒了,不想写代码,所以呢,聊聊从权限管理让我思考的分解系统的一些东西吧,其实也不算是分解系统,仅仅就是想说说松耦合的事情。

曾经我以为我自己了解的不够多,但是通过这半年换工作以后我才意识到我了解的很多了,只不过有很多东西仅仅就是了解的程度而已,没有实践经验并没有什么卵用,而且了解的越多,不知道的就越多,就包括这次的权限控制这些东西,以前我总是认为这是个很高大上的东西,后面的学习我知道了rbac就是角色权限这些东西,现在我又认识到,这些东西应该是可以热插拔的可替换的,不应该对现有系统造成任何影响,也就是说我撤除掉权限相关的东西系统应该可以正常运行下去,还有,其实造一个轮子很难啊,因为可能需要我们为不同的框架去做相应的适配。

学习,永远都停不下来,我也不知道我上面说的东西对不对。就这样吧,看东西去了

对了,最重要的东西没说,说说思路。

首先我们应该有一个baseModel,所有的model对象都继承自baseModel,baseModel里面有hasRole,hasPermission,can ,cannot等这些方法用于权限的验证,然后blade那边我们也应该有相应的控制,至于角色权限相关,就按照自己的思路定义就可以了。

一些都应该是可替换的

我所理解PHP中的container

又用了一次老图片,这个图片我个人真的炒鸡喜欢呢。

说正事吧,10天前,我开始着手写自己的php框架,目前仅仅把路由和mvc中的vc实现了出来,说实话写框架真的不是那么简单的事情,需要考虑的东西要比平时写逻辑多得多。也可能是我考虑的太多,就是那些有的没的很容易中断我的思路,而且总想用上最新的东西,让自己的框架看起来高达上一些,所以最先想实现出来的就是这个container了。框架也许会中断一阵,然后从小的组建弄起,先准备弄一个pdo的封装,然后这个pdo的封装就成为我框架重的m了。

好吧,又跑偏了,继续说container,以前弄java的时候ssh框架就多次提到了ico啊di啊什么的,也许是当时自己的知识不够吧,仅仅是那么用而已,具体为什么却没有去考究,做开发3年多了,现在我也算是理解了。下面就说说我们为什么需要一个container作为全局容器呢。就是为了ioc/di。

那么ioc/di是什么呢,这两个其实是一个意思,就是什么控制反转,依赖注入,这两个词描述的是一个事情,只不过是从不同的方向说的而已。以前我们在开发的时候需要一个什么东西,构造好,传递进去,为什么不说在类里面初始化呢,这也是解耦嘛。现在为什么要依赖注入呢,早年的php框架各自为政没有太多的需求,现在psr标准出来了,composer有了很多的组件让我们来使用,标准就越来越凸显了,大家的选择就有很多,我们在各种标准接口上实现自己的类,就可以无缝替换了。也许有朋友会说。上面的方式也ok啊,改个代码就行了,但是不得不说的是这是极度危险的,当有一处忘记改动了,就会有很大的问题。所以,有一个容器统一管理我们的各种组件就是很重要的了。然后我们修改config中的相关项就可以了,这样就会很安全。

好吧,上面就是我的理解,没有一个代码,真的不愿意贴代码啊。

btw,美好的周六这么过去了,没写代码,现在写点吧,开始弄pdo

在网页上增加角标

<div style="height:50px;position: absolute; top: 0px;right: 0px; overflow: hidden;">
    <div style="font-size: 12px;text-align: center;position: relative; top: -5px;right: -52px; z-index: 999; background: #ff0000;color: #f3f3f3;transform:rotate(45deg);padding: 12px 50px 3px 50px;">Beta</div>
</div>

单纯是公司用到了,需要记录下,具体参数可以自行调整,外部 div 就是为了防止溢出用的了

[转载] php正则表达式实现@某人

PHP正则表达式实现@某人 if(preg_match_all('#@\w+#u', '@张全蛋 含泪质检@三星Note7 被炸飞,听说@炸机 跟@啤酒 更配哦!', $matches)) { var_export($matches); } //输出 array ( 0 => array ( 0 => '@张全蛋', 1 => '@三星Note7', 2 => '@炸机', 3 => '@啤酒', ), ) 正则表达式 #@\w+#u 中:

是分隔符.

u是修饰符,表示Unicode. \w是元字符,在ASCII下等价于[A-Za-z0-9_],在Unicode下表示字符(包括汉字)和数字和下划线. +是量词,表示1个或多个,等价于{1,}