Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Add MIT license.
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
/**
* SplClassLoader implementation that implements the technical interoperability
* standards for PHP 5.3 namespaces and class names.
*
* http://groups.google.com/group/php-standards/web/psr-0-final-proposal?pli=1
*
* // Example which loads classes for the Doctrine Common package in the
* // Doctrine\Common namespace.
* $classLoader = new SplClassLoader('Doctrine\Common', '/path/to/doctrine');
* $classLoader->register();
*
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @author Jonathan H. Wage <jonwage@gmail.com>
* @author Roman S. Borschel <roman@code-factory.org>
* @author Matthew Weier O'Phinney <matthew@zend.com>
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
* @author Fabien Potencier <fabien.potencier@symfony-project.org>
*/
class SplClassLoader
{
private $_fileExtension = '.php';
private $_namespace;
private $_includePath;
private $_namespaceSeparator = '\\';
/**
* Creates a new <tt>SplClassLoader</tt> that loads classes of the
* specified namespace.
*
* @param string $ns The namespace to use.
*/
public function __construct($ns = null, $includePath = null)
{
$this->_namespace = $ns;
$this->_includePath = $includePath;
}
/**
* Sets the namespace separator used by classes in the namespace of this class loader.
*
* @param string $sep The separator to use.
*/
public function setNamespaceSeparator($sep)
{
$this->_namespaceSeparator = $sep;
}
/**
* Gets the namespace seperator used by classes in the namespace of this class loader.
*
* @return void
*/
public function getNamespaceSeparator()
{
return $this->_namespaceSeparator;
}
/**
* Sets the base include path for all class files in the namespace of this class loader.
*
* @param string $includePath
*/
public function setIncludePath($includePath)
{
$this->_includePath = $includePath;
}
/**
* Gets the base include path for all class files in the namespace of this class loader.
*
* @return string $includePath
*/
public function getIncludePath()
{
return $this->_includePath;
}
/**
* Sets the file extension of class files in the namespace of this class loader.
*
* @param string $fileExtension
*/
public function setFileExtension($fileExtension)
{
$this->_fileExtension = $fileExtension;
}
/**
* Gets the file extension of class files in the namespace of this class loader.
*
* @return string $fileExtension
*/
public function getFileExtension()
{
return $this->_fileExtension;
}
/**
* Installs this class loader on the SPL autoload stack.
*/
public function register()
{
spl_autoload_register(array($this, 'loadClass'));
}
/**
* Uninstalls this class loader from the SPL autoloader stack.
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
}
/**
* Loads the given class or interface.
*
* @param string $className The name of the class to load.
* @return void
*/
public function loadClass($className)
{
if (null === $this->_namespace || $this->_namespace.$this->_namespaceSeparator === substr($className, 0, strlen($this->_namespace.$this->_namespaceSeparator))) {
$fileName = '';
$namespace = '';
if (false !== ($lastNsPos = strripos($className, $this->_namespaceSeparator))) {
$namespace = substr($className, 0, $lastNsPos);
$className = substr($className, $lastNsPos + 1);
$fileName = str_replace($this->_namespaceSeparator, DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
}
$fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . $this->_fileExtension;
require ($this->_includePath !== null ? $this->_includePath . DIRECTORY_SEPARATOR : '') . $fileName;
}
}
}

The link in the header comment is broken, the document is now located here:

http://groups.google.com/group/php-standards/web/psr-0-final-proposal?pli=1

till commented Jul 8, 2011

Maybe it shouldn't use require but include in loadClass()? Or is there a reason to do that?

loadClass() method should be changed to return boolean result because current implementation breaks the logic of calling autoloaders in the stack. Here fix for that (PHP >=5.3.3):

$fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . $this->_fileExtension;
$filePath  = stream_resolve_include_path($fileName);
if ($filePath) {
    require $filePath;
}
return $filePath !== false;

How ridiculous. I put it on my blog in case the cache disappears. http://blog.montmere.com/2011/09/21/psr-0-final-proposal/

I've created a gist (md) version of the proposal: https://gist.github.com/1234504

@jonahbron @Thinkscape @till @lisachenko @fuhrysteve Google Groups does not support pages.
The FIG released under a repository, reachable at: https://github.com/php-fig/fig-standards

Cheers,

@till Obviously for the reason that require throws a fatal error (E_COMPILE_ERROR), instead of a warning (E_WARNING) as include does.

bxt commented Nov 1, 2011

@lessthanthree So? You want an E_COMPILE_ERROR from class_exists() calls? I would like to see lisachenko's comment realized. Or maybe another parameter if or not to throw exceptions and errors.

hejrobin commented Nov 1, 2011

@bxt I usually want to halt the execution whenever a non existent file is imported, that's why I'd favor require over include. This gives me more control (or the illusion of...) over the application process flow. :)

bxt commented Nov 2, 2011

@lessthanthree but you might want to pass to downstram autoloaders, and if a class is not found execution is stopped anyway. Maybe the best solution would be checking if the file exists and then requireing?

Here is my version for PSR-0 proposal https://gist.github.com/1335891. It checks existence of the file and have two more features: relative paths and multiple include paths for single namespace. So you can configure loader like this:

$loader = new SplClassLoader('MyVendor', array('../vendor', '../src'));
$loader->register()

Is anyone likely to actually need to change the namespace separator at runtime using those getters and setters? Perhaps I'm failing to understand what use-case that functionality is intended for, but wouldn't it be better to define that value as a constant instead?

@henry-smith Agree with you. Namespace separator is defined as a constant and definitely won't be changed.

@henry-smith and @lisachenko I think you guys are missing the point of the namespace separator. It's not there because you're going to change the namespace separator at runtime. That _would_ be silly. Instead, it exists to support interoperability between different class naming conventions across vendor libraries in the same project.

For example, let's say you use a class from the original Zend Framework in your project. The ZF1 class naming convention uses underscores and looks like Zend_View_Helper. Meanwhile, in your PHP5.3+ awesomeness you utilize the more modern approach of namespacing your classes like mylib\AwesomeClass. The SplClassLoader is providing you a means to autoload both with the same underlying code (hence the name, Standard):

$myLibLoader = new SplClassLoader('mylib', '/path/to/mylib/src');
$myLibLoader->register();

$zendLoader = new SplClassLoader('Zend', '/path/to/zend/lib');
$zendLoader->setNamespaceSeparator('_');
$zendLoader->register();

Obviously, if you only utilize one namespace in your project and don't reference outside vendor libraries you'll never have need of this functionality.

As far as using require instead of include ...

At first I thought include might be better and that require was overkill. After all, you might not want script execution to die with a fatal E_ERROR if a file isn't found. But the more I considered it, I changed my mind.

First, the class loader is only invoked when a class that doesn't already exist is needed for script execution. Your script should die if you reference a class that doesn't exists and it can't be loaded. A missing class definition isn't something that should be _handled_. It's incumbent on the programmer to make sure these files make it in at runtime.

As for the argument that a downstream autoloader might exist to load a class not found (necessitating include over require) ... IMHO this would be a poor practice. The point of the SplClassLoader is to streamline autoloading of class files. There's no reason to waste resources invoking multiple autoloaders when the autoloader explicitly defines how class files should be named in the first place. One class loader per namespace is all that's needed; any more than that is unnecessary at best and terribly inefficient at worst.

Ah, nice clarification. I understand now, thanks. The docblock for setNamespaceSeparator() does kind of imply this too:

Sets the namespace separator used by classes in the namespace of this class loader.

The meaning of that would be absolutely clear to anyone who had that need as well. Kind of presumptuous of me to try to second guess a design decision just because I didn't understand it. Sorry about that!

@lisachenko (comment on July 12, 2011), why do you think that loadClass() must return boolean result?

PHP checks after each autoloader if class exists and if it's true it stop autoloaders queue. See http://www.php.net/manual/ru/function.spl-autoload-register.php#96952

line 52
@return void

should be
@return string

Setters could return $this for method chaining. Powerful in 5.4

(new SplClassLoader())->setIncludePath(realpath(dirname(__FILE__) . '/lib'))
                      ->register();
jahvi commented Jul 8, 2012

I was trying to load the Slim Framework like this:

$classLoader = new SplClassLoader('Slim', 'libs/Slim');
$classLoader->setNamespaceSeparator('_');
$classLoader->register();

But I wasn't able to, I found the problem was the following in Line 123:

$this->_namespace.$this->_namespaceSeparator === substr($className, 0, strlen($this->_namespace.$this->_namespaceSeparator))

The problem is that the statement looks like Slim_ === Slim so it returns false, It works fine with namespaced classes though

If this class is to insist on require() instead of include & return false, and it's being held up as the gold standard by the authors of PSR, then surely for consistency they should deprecate the class_exists() function?

As has been noted by several people above, the autoloader is not called only when a class is needed; it is also called whenever a class' existence needs to be checked, particularly in frameworks and apps that use a plugin architecture. Autoloaders which ignore this are broken.

shrikeh commented Jul 24, 2012

I must agree with @parsingphase. A stacked autoloader implementation with require() must only use require if it first checks with either file_exists() or is_readable().

ichiriac commented Aug 5, 2012

you should use strtr (for a char replace) vs str_replace (more faster) ...

ichiriac commented Aug 5, 2012

SRP principle : you have forget a resolveFileName($class) function, should not be defined in loadClass function ...

javis commented Aug 30, 2012

Wouldn't be better if the path matching with the class name was previously converted to lowercase?
That would resolve conflicts with Windows and would normalize the directory structure too:

Example:

class:
framework\common\Request
path:
framework/common/request.php

Why does the constructor accept NULL value for both parameters? That makes no sense in my opinion.

Krienas commented Oct 10, 2012

This example doesn't follow PSR-2 coding style. I think PSR should refer to examples which follow PSR. There is at least one issue - private members prefixed with "_".

Isn't it a little strange use a construction like "if (false !== ($lastNsPos = strripos($className, $this->_namespaceSeparator)))[...]", with the false statement first? Looks like a little causative (like master yoda talks)

How about having a static class that do all the work for a generic autoloading

public static function autoRegister($ns = null, $includePath = null){
    $instance = new static($ns, $includePath);
    $instance->register();
}

The comment of @parsingphase is so true!
You have to use include instead of require for 3 reasons:

  • Usage of class_exists, interface_exists & trait_exists are broken
  • Spl autoloading stack may contain other type of loader to support different naming convention (like Pear), using require force the end user to put you loader at the bottom of the stack, else it broke everything
  • Using require will trigger a fatal error at line 133 of file SplClassLoader.php, WTF : how can I find where the missing or mispelled class is in my code, now?

Just in case you need a lightweight solution to autoload PSR-0 and Zend like libs, take a look at this gist.

It's extreme, it's not following PSR-1 / PSR-2 coding standards at all, but it's short, fun and effective :

<?php 
set_include_path(get_include_path().PATH_SEPARATOR.__DIR__);//optional
spl_autoload_register(function($c){
    @include preg_replace('#\\\|_(?!.*\\\)#','/',$c).'.php';
});

Thanks for the useful autoloader!

This class should be updated to match the the PSR-2 standard

Very useful~

jjok commented Apr 24, 2013

This is the way I do it. It only requires the file if its on the include path, so you get your error messages in the right place and multiple autoloaders can be used, like in @adriengibrat's comment.

<?php
spl_autoload_register(function($class) {
    $parts = explode('\\', $class);

    # Support for non-namespaced classes.
    $parts[] = str_replace('_', DIRECTORY_SEPARATOR, array_pop($parts));

    $path = implode(DIRECTORY_SEPARATOR, $parts);

    $file = stream_resolve_include_path($path.'.php');
    if($file !== false) {
        require $file;
    }
});
voleye commented Jul 30, 2013

Current implementation implicitly shows that there is only one include path possible for one Vendor Name

But let`s imagine I have the following structure of the code
folder1/Vendor Name 1/Namespace 1/...
folder2/Vendor Name 1/Namespace 1/..
folder2/Vendor Name 1/Namespace 2/...

There is no one rule from PSR-0 has conflict with this case:

  • A fully-qualified namespace and class must have the following structure ()*
  • Each namespace must have a top-level namespace ("Vendor Name")
  • Each namespace can have as many sub-namespaces as it wishes.
  • etc...

You cannot have the same namespace in two folders, like your Vendor1. You might be lucky and have Subnamespaces below that Namespace1, and can then use this loader.

Otherwise, you'd have to solve the question of "how do you know where the file for a class is", and if the answer is "I start looking in folder1, and if nothing is found, continue with folder2", that might work, but is is not really PSR-0 compliant.

And it isn't feasible as well. What if the same class exists in both folders?

voleye commented Jul 31, 2013

What do you think is it make sense to add this rule item into PSR-0 standards?
P.S. I guess here is an answer https://github.com/php-fig/fig-standards/blob/f4d92e6b4003f42c20403e078e9d5bbde2e984cc/proposed/autoloader.md

I think "include" is more logical, after all the method class loader utilises to load a class should be encapsulated. No one needs to know about that, so using "file_exists" is the best.

If the class is not found with your class load the php will keep searching until it reaches the last class loader, you don't need to worry about boolean return either. A fatal error will be generated if PHP not found the class anyway. Don't forget that some libraries use their own class loaders and using "require" will break them.

pls add a licence ( LGPL / MIT / BSD / GPL / AGPL )

Am I missing something here (I'm a namespace noob)?

Using a namespaced class such as Zend\XmlRpc\Server.php (use Zend\XmlRpc) and stepping through the code, the function :

tryLoadClassByPath($className, $unresolvedFilePath) 

always returns false for namespaced classes because the $className parameter is the bare
class name without its namespace prefix so fails the test.

$isFound = class_exists($className, false);

If I modify the calling code to use the namespaced class like this:

public function loadClass($className)
{
        $originalClassName = $className;

... and use this later ...

                $isFound = $this->tryLoadClassByPath($originalClassName, $unresolvedFilePath);

then it returns true as expected. The class file is successfully loaded in any case as the file path is correctly setup so you don't actually see much happen even if the function does return false but I am fairly certain it should return true.

There was a problem with constants I'd defined. My BASE_PATH CONST was defined like this

define('BASE_PATH', dirname(realpath(__FILE__)) . '/');

So when I instantiated your class like this

$classLoader = new SplClassLoader('App\Controllers\Content', BASE_PATH);

The trailing Slash from my BASE_PATH Const was the causefor an error.
To modify this class by adding a rtrim() to the __construct method worked for me:

public function __construct($ns = null, $includePath = null)
{
    $this->_namespace = $ns;
    $this->_includePath = rtrim($includePath, '/');
}

Perhaps something for the next Version.....no one needs trailing slashes :)

bstoots commented Dec 2, 2013

As ClemensSahs mentioned I would also like to know the license for this code.

saji89 commented Jan 6, 2014

Please remove the http://groups.google.com/group/php-standards/web/final-proposal link in the file, it leads to a 404 page.

Zeokat commented Mar 1, 2014

Zeokat very usefull piece of code. I need to know the license because i thinking into using it.
Thanks.

Hi All,

Why declared methods setIncludePath / getIncludePath and not declared methods getNamespace and setNamespace in this SplClassLoader ??

Regards

Download file of this gist don't have white line in the end file. See PSR2

This link
https://gist.github.com/r2pq/aebc8131955ee819086f

Is a fork to this file with the next PHPDoc fixes:

  • The constructor has two parameters, this was not reflected before.
  • The PHPDoc related to getNamespaceSeparator(), had a typo. It said 'seperator' instead of 'separator'.
  • The PHPDoc related to getNamespaceSeparator(), now has a return string instead of return void.

I think, there is a problem with this implementation or the PSR-0 itself. When - theoretically - using classnames that begin or end with and underscore "", e.g. \Vendor\Namespace_Class_Extension.php

This would end up in something like this:
Vendor/Namespace//Class/Extension/.php and will definitely fail to load.

There is no omission or MUST NOT for leading or trailing underscores in the definition of classnames.

To avoid this, just replace the line:

<?php

$fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . $this->_fileExtension;

with

<?php

$fileName .= preg_replace("#([a-z0-9]+)_([a-z0-9]+)#i", "$1" . DIRECTORY_SEPARATOR . "$2", $className) . $this->_fileExtension;

and the example above will end up in:

Vendor/Namespace/_Class/Extension_.php

keradus commented Aug 8, 2014

Maybe useful:
https://github.com/keradus/SplClassLoader - taken from PSR and little fixed like docs etc.
If need anything to change just PR.

Btw, consider using PSR4 autoloader:
https://github.com/keradus/Psr4Autoloader

Dabnis commented Dec 20, 2014

Here is the file loading solution I use within my autoloader:
REM** $class can be a Class OR an Interface

include $filename;
// Check to see if the include declared the class
if(!class_exists( $class, FALSE )) {
// Check to see if the include declared the interface
if(!interface_exists( $class, FALSE )){
// We do not have the class nor interface : file did not load or did not exist
// Do something : Log error; check other file resources or die gracefully.
}
}

Why I use this:
Loading files is normally the slowest part of any php application. Using file_exists(), even though it does not load the file still takes 'almost' as much time as loading the file itself (assuming a small file which class files tend to be >10Kb). So if you use:

file_exists(fileName){
require fileName;
}

From a application run time perspective its like loading the file twice !!! In addition require will kill your app if it fails where include only throws a warning.

Testing if the class or interface exists after an include will give you options to try something else before YOU gracefully exit your application.

Hope this helps

Got it.

roquie commented Feb 4, 2015

Maybe replace 'require' with 'require_once' in loadClass(...) method ?
http://php.net/manual/en/function.require-once.php

Hi guys, I have trouble.
Using SplClassLoader loosing displaying of parse errors and it is really pain the ass to debug the code manually. I can only catch then with register_shutdown_function(). I have set error_reporting to E_ALL, I did almost everything to display them and nothing.

If I require the classes manually everything works as expected (any errors are displayed). Any suggestions?

v3u3i87 commented Apr 16, 2015

get class in to on?

SplClassLoader line 133 is throwing error. File not found. But the file is present in another folder, how to fix this

klaussantana commented Oct 4, 2016 edited

Hello there people! New important info forgotten here..

The method loadClass on line 141 must return a boolean value! Recomended:

return class_exists($className, false) || trait_exists($className, false) || interface_exists($className, false);

This is imperative! PHP SPL autoload function suports bubbling effect.. You can register as many autoload functions as necessary, so, you must return a boolean value to indicate if the function loaded the resource or not. If it returns true, the SPL stops casting autoload functions.. if it returns false it keeps goin on the rest of the functions registered through spl_autoload_register.

The second parameter in every *_exists methods refer to autoloading and it must be false inside the autoload function scope to avoid infinite loop (the autoload function casting itself). If you try to load an interface, class_exists will drop false, because it's not a class, but interface_exists will drop true, then the autoload stops trying to load that resource.

Also, it's recommended to use include_once than any of other options. For instance, if you try to load My\Personal\Class\Name and My_Personal_Class_Name as the code is it will drop a fatal error trying to duplicate the resource (class, trait or interface). The fix proposed here will solve this problem.

See ya!

Best vinyl cutting machine – How to easily buy the best vinyl cutting machine?
Best vinyl cutting machine

Bộ Khóa cửa điện tử của Yale có hình thức đẹp và nhiều chức năng. Thiết kế bóng bẩy và tối giản

gialamphat commented Mar 15, 2017 edited

Mạ niken bóng là một trong những quy trình mạ khép kính nhất vì bể mạ niken thường rất ô nhiễm và mang lại độc hại cho người sản xuất,
Công ty chúng tôi với uy tín là trên hết ,nên công ty chúng tôi hiện nay được nhiều người quan tâm và để ý đến và chúng tôi hiện bây giờ đang là một công ty lớn nhất thị trường hiện nay ,với một công ty có một đội ngủ nhân viên chuyên nghiệp được chúng tôi đưa đi học ở nước ngoài về ,và toàn bộ máy móc trong công ty chúng tôi đều được nhập từ ngoài nước và cũng chưa có một công ty nào có thể sử dụng được .
vì vậy khách hàng có nhu cầu về lớp mạ niken trên các sản phẩm thì vui lòng liên hệ chúng tôi qua website :https://giacongxima.com/cong-ty-xi-ma-niken

gialamphat commented Mar 27, 2017 edited

Công nghệ xi mạ là một trong những ứng dụng khoa học tiên tiến nhất giúp cho các bề mặt của sản phẩm được bóng sáng và tránh được những thời tiết khắc nghiệt,ngoài ra mạ crom còn giúp cho sản phẩm chịu đựng được ở những nhiệt độ cao mà lớp mạ vẫn bình thường không bị bong tróc hay trầy xướt đó là lớp mạ crom mà các nghành công nghiệp đang ứng dụng rát rộng rãi.
Dịch vụ gia công xi mạ crom cứng trên kim loại https://giacongxima.com/xi-ma-crom-cung/

công nghệ mạ crom là một trong những công nghệ mạ tiên tiến nhất trên thị trường hiện nay với một lớp mạ dầy giúp cho cac sản phẩm được bóng đẹp như gương và chịu mài mòn rất tốt,ngoài ra mạ crom còn giúp cho các sản phẩm chịu được ở những nhiệt độ cao.
lớp mạ crom hiện nay được cả thế giới tin dùng vào các sản phẩm thông dụng như là trục ty ben ,trục lô cán,trục piston,và những chi tiết máy móc cần độ chính xác mài mòn cao đều được mạ crom https://giacongxima.com/ximacrom5/

gialamphat commented Mar 28, 2017 edited

một số quy trình cũng như là công nghệ mạ niken crom hiện nay trên thị trường chúng ta cần tham khảo http://gialamphat.com/ma-niken-bong-1/ Các sản phẩm xi mạ niken sẽ bền hơn khi sản phẩm được thụ động thêm lớp Crom , và phủ keo cho bề mặt sản phẩm. Còn đối với những sản phẩm cao cấp đòi hỏi bề mặt xi mạ phải dày bóng đẹp thì trước khi mạ Niken ta mạ phủ thêm lớp đồng.ngoiaf một số công nghệ mạ hiện nay chúng tôi còn cung cấp thêm một số dịch vụ gia công cơ khí sửa chữa và phục hồi ty ben thủy lực,và một số chi tiết chuyên về cơ khí các quý khách có thể tim hiểu ở đây http://gialamphat.com

KenLil commented Mar 29, 2017

http://cuanhomkinhnhatrang.com truy cập vào website chúng tôi để mua những cửa kính xingfa đẹp nhất và bền vững với thời igan

tienban commented Mar 31, 2017

Dòng máy photocopy konica đang dần là dòng máy photocopy hiện đại và sử dụng các công nghệ khoa học kỹ thuật để nhằm đáp ứng các tiện ích của dòng máy photocopy của hãng này. Với sự thân thiện của người dùng, máy photocopy đang dần trở nên là một xu hướng hiện nay. Để xem thi tiết các dòng máy photocopy konica, các bạn có thể xem tại đây http://haiminhco.com.vn/may-photocopy-konica-minolta

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment