Background
I need to design a set of utility classes that will ease out file system access to several types of remote services (Local, FTP, WebDAV, SVN, SSH), and I cannot seem to get a "perfect" design mostly because of a language barrier - I feel that every design path I have chosen was scraped because PHP did not have support for a specific feature.
I find this frustrating and I'm out of ideas.
Requirements
A base abstract class or interface for FileSystem which every other file system type inherits. The FileSystem class (and it's subclasses) are a 'driver' that implement basic FS operations such as 'move', 'remove', 'create' and so on.
Those driver operations should not be exposed to the class user, instead, FileSystem is also a factory that fetches file info records upon request.
A file is also based on an abstract class or interface File and Directory, which the implementors of a FileSystem subclass may or may not subclass. Users of a FileSystem class do not 'care' about the type of a File (SshFile and SvnFile should work the same).
The File (and Directory) class should be the one to talk to the file system driver that created it in some way without the user being able to do it manually through the FileSystem driver.
What I have tried
Naturally I made FileSystem an abstract class, then proceeded to code the class File and turns out that PHP has no friend support, so File cannot have access to FileSystem's protected driver methods.
Another idea that got scraped was a class inside a class - FileSystem and FileSystem::File - PHP does not have nested classes.
Divide FileSystem into FileSystemDriver and FileSystemFactory, but that gets me into the same original problem.
300 Reputation points in bounty reward
To a solver of an original idea to PHP's lack of programming utilities needed for encapsulation.
Why can't you separate FileSystemDriver from FileSystem? For example:
class SSHFileSystem {
private $driver = new SSHDriver();
function getFile($path)
{
return new SSHFile($this->driver, $path);
}
}
Documentation problem, not code problem.
Leave the FileSystem's methods as public but document that they should only be used by internal File and Directory objects.
This is very common in languages. Python has magic methods, or the _function notation, etc.
If I'm a user, I don't just look at every available method and start using it if it sounds fun, I read the docs and do what they say.
Related
I don't use any famous framework and I've created my own one. It is actually kind of a advanced MVC. Anyway, Its routing is totally different than other frameworks. I mean, there isn't any file named route containing all routes. It will call the needed class and function automatically based on the URL. For example:
http://example.com/classname/methodname/arg1/arg2
URL above calls this script by using autoloader:
class classname{
public function methodname() {
// do stuff
}
}
See? Everyone will know the name of class or method in my script. My question is, is that secure enough? Or is there something particular that I should know?
It depends. If your framework allows arbitrary classes and function to be invoked, this means you effectively build a remote shell with a web interface. In other words: maximum insecure.
Likewise, if your application also allows file uploads, a malicious user might be able to upload their own PHP classes and then invoke it from your URLs. Without further details about your framework, it's hard to say. But you probably want to whitelist what can be called this way.
Even if the framework has some protection measures to control what code can be called, I am not a friend of leaking implementation details like this to the public. You tied your public API to a specific implementation with this. That will make it harder to change the code while maintaining the same API for clients to call.
I'm relatively new to PHP, and I'm looking for a way to define certain objects as globally accessible from throughout the project, from multiple PHP scripts.
In Java, if I've to access some objects globally, I define some public class named Globals , and define the objects that I need to access as static in the Globals class. I then access those objects wherever I need with: Globals.variable_name .
So basically, I need to initialize these global objects only once and then use them whenever I need them..
One use case:
I've a class named Logger that has methods to log certain events in a log file. I need to have 1 Logger instance/object that can be used by all the PHP scripts in the project, whenever they've to log something. I'd rather not have each PHP script using it's own instance of Logger.
The naive Java-like approach I tried, that did not work:
I created a public class named Globals in a separate PHP file (named Globals.php) with one static object of type Logger, named $logHandle. I included this PHP file in all other PHP files where I need this Logger object. I then tried to access this object , using Globals->logHandle from one of the other PHP scripts. This attempt failed miserably.
Is there some similar approach?
Thanks.
PHP is not Java.
In web applications, the PHP environment is initialized for each request - and each request is handled in a different process with it's own memory space. It is possible to share access to data (including serialized objects, but not resources such as database connections and file handles) across different instances. You probably know this already but have not yet realised how it influences the way you write code.
I'd rather not have each PHP script using it's own instance of Logger
Why not?
One very good reason is that allowing multiple processes to write to the same open file handle requires locking to prevent the file getting all mesed up. BUT THIS IS PHP - STOP REINVENTING THINGS FROM SCRATCH. Writing to stderr will append the details to the webserver error log or use the OS syslog facilities - that's what they are there for.
It is impossible to have the same object available to all instances of PHP - you can unserialize an object in all instances - but then it's not the same object. You can run a daemon with a single object which might be accessible to all other PHP instances via a socket connection - but it's not running in the same address space.
If you validly have a class that you want to be universally available via an object with a fixed name, then simply create an instance of the object in each script or via an include file. The approach you tried is the way to go about this (but don't name your objects with reserved words). We don't know why it failed because you didn't provide any error messages or code.
I assume you're asking about common case (now only web-oriented application). And for that - no, you can not define some thing like you've described, in native way. This is the thing that is called superglobals in PHP.
But on the other hand - you need to do that for some reason, not "just because you want it". And, if so - then use configuration file. Create some application configuration file, read it once at start of application (bootstrap) and you'll get all needed values. I'm not saying anything about file structure - it can be xml/ini/yaml/whatever/you/like. But the idea is - to split this logic from application structure itself and use separate file for storing desired values.
Another option is to use some separate PHP file(s) and include it at bootstrap. Define all needed variables/constants in that file(s) and you'll get similar behavior. Note, that in terms of namespaces it's less "global" and you'll need to resolve all that cases manually.
For web-applications, however, one of possible solutions may be using sessions. $_SESSION is a superglobal array and it will behave like you want (i.e. will be accessible from everywhere). But that is not applicable always - and not always you'll want to deal with sessions to store session-independent data.
you can do like this
you said that you have included in all other classes change methods in your global class to static
<?php
class Logger {
public static function log($msg) {
// ...
}
}
you can use it like
Logger::log($msg);
http://www.php.net/manual/en/reserved.variables.globals.php
i think that is what you're after.
To access a static attribute in PHP you need to call it with the Class::$attribute notation, and the static methods need to be called with the Class::method() notation.
The -> notation is used when calling attributes of a class instance.
I would like to extend the cakephp logging facility.
Using
$this->log($msg, $level)
you can log a $msg with $level to tmp/logs/$level.log.
How I would like to use logging is:
Separate functions for different levels, e.g. $this->debug($msg) for $this->log($msg, 'debug') and $this->error($msg) for $this->log($msg, 'error') etc. for logging.
Automatically put the name of the class in front of a message, e.g. $this->debug($msg) will lead to "MyClass: $msg" if $this is of type "MyClass".
I know I can extend the functionality by extending AppModel, AppController etc., but as I need the functionality everywhere in my application, I would rather need to extend cakephp's Object - but didn't find a stable mechanism for that (don't want to change it in the cake/ folder).
I though about implementing a new class for that functionality, but I'm not sure how to make that available in cakephp.
Could you please give me some hints where/how I can implement these extensions neatly?
Global convenience methods
Well, you can't really do monkey patching in PHP (5.2 at least), and that is probably a good thing for the the developers that have to maintain your code after you are gone. :)
CakePHP - being an MVC framework with strict conventions - makes it hard for you to break the MVC paradigm by only allowing you the extend the parts you need in isolation (ie. AppModel, AppController, etc.) and keeping the object-orientated foundation untouched in the core (making it hard to add code that "can be used everywhere" for potential misuse).
As for adding functionality that transcends all the MVC separation, the place for this is app/config/bootstrap.php. When you place code here it seems clear that it is not part of the framework (quite rightly so), but allows you to add these sort of essentials before CakePHP even loads. A few options of what to do here might be:
Create a function (eg. some custom functions such as error() that call CakeLog::write() in the way you like.)
Load a class (eg. load your very own logging class called something like.. Log, so you can call Log::error() in places)
See below:
The logger API
Cake does allow for many customisations to be made to things like the logger, but unfortunately the API exposed to us is already defined in the core in this case. The API for logging in CakePHP is as follows, and you can use either approach anywhere you like (well, the former only in classes):
$this->log($msg, $level) // any class extending `Object` inherits this
// or
CakeLog::write($level, $message); // this is actually what is called by the above
The arbitrary $level parameter that you are trying to eliminate is actually quite a powerful feature:
$this->log('Cannot connect to SMTP server', 'email'); // logs to app/logs/email.log
// vs
$this->email('Cannot connect to SMTP server'); // ambiguous - does this send emails?
We just created a brand new log type without writing an extra line of code and it's quite clear what the intention of our code is.
Customising the logger
The core developers had the foresight to add a condition allowing us to completely replace the logger class should we wish to:
function log($msg, $type = LOG_ERROR) {
if (!class_exists('CakeLog')) { // winning
require LIBS . 'cake_log.php';
}
// ...
As you can see, the core CakeLog class only gets instantiated if no such class exists, giving you the opportunity to insert something of your own creation (or an exact copy with a few tweaks - though you would want to sync changes with core - manually - when upgrading):
// app/config/bootstrap.php
App::import('Lib', 'CakeLog'); // copy cake/libs/cake_log.php to app/lib/cake_log.php
The above would give you full control over the implementation of the CakeLog class in your application, so you could do something like dynamically adding the calling class name to your log messages. However, a more direct way of doing that (and other types of logging - such as to a database) would be to create a custom log stream:
CakeLog::config('file', array(
'engine' => 'FileLog', // copy cake/libs/log/file_log.php to app/libs/log/file_log.php
));
TL;DR - Although you can load your own code before CakePHP bootstraps or for use in isolation in each of the MVC layers provided, you shouldn't tamper with the object hierarchy provided by the core. This makes it hard to add class methods that are inherited globally.
My advice: use the API given to you and concentrate on adding more features instead of syntactical subtleties. :)
In addition to what deizel said (great writeup, by the way, deizel), you don't have to use Cake's logger. You're welcome to use any logging system you'd like. Choosing an existing logging framework that you like would probably be the safest bet. bootstrap.php is a good place to do any require calls or initializations.
Otherwise, if you'd like to do things 'in' Cake, I'd recommend creating a plugin with a trio of logging interfaces: a Component, a Behavior, and a Helper. That way the logging functionality would be available in your models, views, and controllers. As for how to code it, I like the idea making the cake classes thin proxies to your real logging class, and using the magic method __call() in your proxies to parse logging requests and environment, then forward on that information to your logger to handle uniformly.
You'd be able to write things like $this->MyLogger->oops("stubbed my toe") and potentially have an oops.log file with your messages and whatever additional information (calling controller/view/model, time&date, etc.) you'd like included.
When using dependency injection for database handlers etc instead of singleton - where is it best to keep the configs I.e. Username password host etc. Keep inside the class, use a container class or use a static configs class or use a file?
I generally keep them in a file outside of the webroot.
External config file that returns an array is a quick solution:
config.php:
<?php
return array(
'database'=> array(
'host'=> 'localhost',
'dbname'=> 'name_of_db',
'username'=> 'myusername',
'password'=> 'mypassword',
),
);
test.php:
<?php
$config = include('config.php');
mysql_connect($config['database']['host'], $config['database']['username'], $config['database']['password']);
....
Ideally, store the config file in a directory that can be read by the anonymous web user (but not written).
This is difficult to get 'right' because it depends on the exact use-case. But here's what I did when I had a very similar problem.
I had setup a shared library system for a small number of websites. Initially, it provided just a database handler, but there was quickly added an ORM layer. Most of the growth after that was additional objects as one of the websites was rewritten away from direct SQL into object-based access. There was also a configuration library used to define how the objects and other elements in the shared library were collected together into 'modules' as well as default settings for things like database settings. It also supported loading a configuration file outside the code-tree, which was used to per-host settings.
Since the objects in the ORM layer had to configure themselves (there was a static call to do that as they were declared), it was trivial for it to be extended to request a particular database by name, too. Then it was a matter of making sure that all these database definitions were declared as well (and were overridable thanks to the generic configuration mechanism).
(It took a while, but when we did reach the point of having to split the database, it was very easy to point the relevant objects off to another database and everything just kept working.)
To answer your question, though: the configuration was split. Database hostnames, usernames and passwords were all named and defined in one place in the in-code configuration area. But they could be overridden on a per-host basis. Per object settings were where the objects were declared. And that was also where the database configurations were specified by name.
I'm starting to write a application in php with one of my friends and was wondering, if you have any advice on how to implement module support into our application.
Or is there a way how to automatically load modules written in php by a php application? Or should i just rely on __autoload function?
And we don't need plugins that are installable via a web interface, we just need some clever way to associate web pages of our project to some classes (they will render the page) so index.php can call the right class and retrieve it's generated sub-page.
And we are not using any kind of framework, for now at least.
Re your comment: Autoloading in conjunction with strict file naming would be totally enough in this case IMO.
You could define a specific namespace (not in the new PHP 5.3 namespace way, just in the sense of a common prefix to names), e.g. Module_* for module class names.
You could then organize your modules in directories with class files that contain exactly one class definition, for example:
/modules/Mail/index.php // defines class Module_Mail
/modules/Database/index.php // defines class Module_Database
/modules/Image/index.php // defines class Module_Image
Your autoloader function would then, whenever a Module_* class is requested:
$Database = new Module_Database("localhost", .....);
include the correct file from the right directory.
That's the way e.g. the Zend Framework does it, and does so pretty well.
Consider choosing a more specific namespace than Module_ to assure interoperability with other scripts and applications if that is an option in the future.
Slightly related: In a PHP project, how do you organize and access your helper objects?
It sounds like you are looking for a way to organise the different tasks that each page needs to carry out. In this case, take a look at the MVC pattern. It provides a simple way to seperate your data access (models) and how you render/present information (views).
You can map pages to functions with ease. If you store the information in an array of mapped values, and then use a function to compare the requested URL with each of the URLs in the array. Such an array could look like this:
$urls = array(
'/' => 'index',
'/aboutus/' => 'aboutUs',
);
There are several articles that discuss how to implement it in PHP in a couple of hours. This article is a very simple guide. I am not a fan of their use of the registry pattern but reading through should provide you with enough information as to how you can implement it yourself.
Regarding autoloading, you can also use spl_autoload_register, to define several (more than one) autoload functions, so each module can set up it's own implementation of autoloading.
All you need here is an MVC architecture, with one Controller class associated with each "module".
If you are not against using a framework, go for Zend MVC
It allows you to have the following principle :
An URL like this : http://yoursite.com/my-module/edit
Will more or less automatically call the editAction method in the MyModuleController class.