Asked  7 Months ago    Answers:  5   Viewed   56 times

I'm currently working on my own PHP Framework, and I need some help figuring out if I'm going in the right direction or not...

The framework is both for my own use and to generally advance my PHP skills further. I've encountered numerous problems that by overcoming them I have learned a great deal, and love being able to create something from nothing, so I'd rather not see answers like "Just use Zend"! ;)

I have read a bunch of articles both on Stack Overflow and a bunch of other sites, but can't quite get the right answer I need, so hopefully someone can give me some helpful advice!

I've tried a few different solutions, but I've just ended up confusing myself and I'm not sure which direction to go now! Can't quite get my head around it all...

'Theoretical' framework structure

- .htaccess
- index.php
- private/
    - app/
        - bootstrap.php
        - modules/
            - default/
                - controllers/
                    - pages.php
                    - index.php
                - models/
                - views/
            - admin/
                - controllers/
                - models/
                - views/
    - config/
        - config.php
        - autoloader.php
    - lib/
        - Some_Library
            - Class1
                - class1.php
            - Class2
                - class2.php
- public/
    - css
    - images
    - scripts

Details

  • index.php is the main file, where every request is routed to with .htaccess.
  • private/ can't be accessed publicly, obviously.
  • public/ contains all the public files.
  • app/ contains all app-specific code.
  • lib/ could contain Zend or another library (I'm also working on my own), to be called with autoloaders
  • bootstrap.php is the app-specific code... Do I need this? is the main 'index.php' enough?.
  • modules/ would contain each module... Do I need modules at all?.
  • default/ is the default module that will contain the MVC's for most requests (used when 'admin' isn't the first part of the URL).
  • admin/ is the module that will contain the MVC's for the admin section.

Anyways, to my question...

I thought it would be better to separate the admin section from the rest of the website, but that's where I'm getting stuck. I have made the above structure to work with it, but I'm not sure if this is the most effective way.

If a request site.com/videos/view/1/ comes to my site..

Module: Default Controller: Videos Action: View Params: array( '1' )

and if the request site.com/admin/pages/view/1/ comes to my site..

Module: Admin Controller: Pages Action: View Params: array( '1' )

Is this the right way to go about this? Or am I over-complicating it and doing something that's not worth doing?

Should I have a completely separate application framework for my admin section...? Do I even need to separate the admin section's MVC's from the rest of it all?

Sorry for the massive question, just wanted to give you as much info as possible! Feel free to answer whichever part you can =P

 Answers

25

One Solution for admin routing is what CakePHP does, you first define a configuration for the admin string and then in your controller use actions with a specific naming convertion

//Configuration ============================
Configure::write("admin_routing" , true );
Configure::write("admin_prefix"  , "admin" );

//Controller ===============================
class MyController extends AppController{

    function index(){
      //Will map to /mycontroller/
    }


    function admin_index(){
      //Will map to /admin/mycontroller/
    }

}

You can generalize this by using a routing system just look how your favorite framework does it

On another note

  1. The modules folder seems to be unecesary
  2. I agree with antpaw you should add a globals view and model folder in order to share them across applications
  3. I don't get why autoloader is inside the config directory and not as part of the lib directory, you can also just move the boostrap.php to the config directory

Hope this helps

Wednesday, March 31, 2021
 
DaveRandom
answered 7 Months ago
37

I'm using a similar structure (with homemade framework too but backup out of webroot). You could maybe add a "form" folder in the private folder.

I use this to make controller more readable. The forms are generaly a big wall of object code. Putting them in an external file included in the controller is a good idea.

Don't forget to exclude the public folder from the rewriting rules and everything should be allright :)

An other solution is to put index.php in your public folder and define this folder as your webroot in nginx. It prevent remote access to all other file (like backup file) which should used only by the framework.

/applications
    /administration
        /private
            /controllers
            /models
            /views
            configuration.php
        /public <---- Vhost WebRoot
            /ajax
            /fonts
            /icons
            /images
            /stylesheets
            index.php
    /website
        /private
            /controllers
            /models
            /views
            configuration.php
        /public <---- Vhost WebRoot
            /ajax
            /fonts
            /icons
            /images
            /stylesheets
            index.php
/backups
/library
    /helpers
        datetime.php
        text.php
    controller.php
    model.php
Wednesday, March 31, 2021
 
Semirix
answered 7 Months ago
46

Ok I'm just gonna start by saying I'm no expert in PHP and I recommend using a framework such as symphony to do your routing, but here is one possible solution that you could use.

function regexPath($path)
{
    return '#' . str_replace([":int:", ":string:"], ["d+", ".+"], $path) . '#';
}

function parseRequestedView($url)
{
    $ressource_requested = explode('/', trim($url, '/'));


    // define our routes, and the indices that each route will use (from the exploded url)
    // this could be defined as another parameter or as a member of the class
    $routes = [
        regexPath("article/:int:/comments/show") => [0,  2, 3,  1], // will return array(resource[0], resource[2], resource[3], resource[1]), etc
        regexPath("article/:int:/show")          => [0, -1, 2,  1], // -1 will return a null
        regexPath("archive/show")                => [0, -1, 1, -1]
    ];


    // go through each route, checking to see if we have a match
    foreach ($routes as $regex => $indices)
    {
        if (preg_match($regex, $url))
        {
            // it matched, so go over the index's provided and put that data into our route array to be returned
            foreach ($indices as $index)
            {
                $route[] = $index > -1 ? $ressource_requested[$index] : null;
            }

            // include the post data (not really nessesary)
            $route[] = $_POST; // unnessesary to pass $_POST data through function, because it is global

            return $route;
        }
    }

    return null; // or some default route maybe?
}

$route = parseRequestedView("article/13/comments/show");

echo '<pre>';
print_r($route);
echo '</pre>';

/* returns:
Array
(
    [0] => article
    [1] => comments
    [2] => show
    [3] => 13
    [4] => Array // this is our $_POST data
        (
        )

)
*/
Wednesday, March 31, 2021
 
PLPeeters
answered 7 Months ago
13

I personally subclass both Zend_Db_Table_Abstract and Zend_Db_Table_Row_Abstract. The main difference between my code and yours is that explicitly treat the subclass of Zend_Db_Table_Abstract as a "table" and Zend_Db_Table_Row_Abstract as "row". Very rarely do I see direct calls to select objects, SQL, or the built in ZF database methods in my controllers. I try to hide the logic of requesting specific records to calls for behind Zend_Db_Table_Abstract like so:

class Users extends Zend_Db_Table_Abstract {

    protected $_name = 'users';

    protected $_rowClass = 'User'; // <== THIS IS REALLY HELPFUL

    public function getById($id) {
        // RETURNS ONE INSTANCE OF 'User'
    }

    public function getActiveUsers() {
        // RETURNS MULTIPLE 'User' OBJECTS            
    }

}

class User extends Zend_Db_Table_Row_Abstract {

    public function setPassword() {
        // SET THE PASSWORD FOR A SINGLE ROW
    }

}

/* CONTROLLER */
public function setPasswordAction() {

    /* GET YOUR PARAMS */

    $users = new Users();

    $user = $users->getById($id);

    $user->setPassword($password);

    $user->save();
}

There are numerous ways to approach this. Don't think this is the only one, but I try to follow the intent of the ZF's design. (Here are more of my thoughts and links on the subject.) This approach does get a little class heavy, but I feel it keeps the controllers focused on handling input and coordinating with the view; leaving the model to do the application specific work.

Tuesday, July 27, 2021
 
Juriy
answered 3 Months ago
90

If you're trying to validate that an HTTPS certificate is valid, HttpWebRequest can do that for you.

To make HttpWebRequest check the revocation status you need to set the global ServicePointManager.CheckCertificateRevocationList = true before calling GetResponse() (I think it's GetResponse, as opposed to the call to Create()).

That will then check:

  • The certificate chains to a trusted root
  • The certificate is not expired (and other such things)
  • The request hostname matches what it should

Which is all three points that you asked about. The hardest one is getting the hostname matching correct, because

  1. There can be several SubjectAlternativeName DNS entries, and there's not a good way to ask about them in .NET.
  2. Any SubjectAlternativeName DNS entry is allowed to have a wildcard (*) in it. But subject CN values are not (and the .NET APIs don't indicate which type of name you got back).
  3. Name normalization for IDNA, et cetera.

In fact, the only thing that HttpWebRequest doesn't automatically do for you (unless you set the global) is check revocation. And you can do that via

HttpWebRequest request = WebRequest.Create(uri) as HttpWebRequest;
request.ServerCertificateValidationCallback = ValidationCallback;

private static bool ValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
    // Since you want to be more strict than the default, reject it if anything went wrong.
    if (sslPolicyErrors != SslPolicyErrors.None)
    {
        return false;
    }

    // If the chain didn't suppress any type of error, and revocation
    // was checked, then it's okay.
    if (chain.ChainPolicy.VerificationFlags == X509VerificationFlags.None &&
        chain.ChainPolicy.RevocationMode == X509RevocationMode.Online)
    {
        return true;
    }

    X509Chain newChain = new X509Chain();
    // change any other ChainPolicy options you want.
    X509ChainElementCollection chainElements = chain.ChainElements;

    // Skip the leaf cert and stop short of the root cert.
    for (int i = 1; i < chainElements.Count - 1; i++)
    {
        newChain.ChainPolicy.ExtraStore.Add(chainElements[i].Certificate);
    }

    // Use chainElements[0].Certificate since it's the right cert already
    // in X509Certificate2 form, preventing a cast or the sometimes-dangerous
    // X509Certificate2(X509Certificate) constructor.
    // If the chain build successfully it matches all our policy requests,
    // if it fails, it either failed to build (which is unlikely, since we already had one)
    // or it failed policy (like it's revoked).        
    return newChain.Build(chainElements[0].Certificate);
}

And, of note, as I put in the sample code here, you only need to check the return value of chain.Build(), because that will be false if any cert is expired or whatnot. You also may want to check the root cert (or an intermediate, or whatever) out of the built chain for being an expected value (certificate pinning).

If the ServerCertificateValidationCallback returns false an exception is thrown on GetResponse().

You should try your validator out to make sure it works:

  • Pick your favorite https site and make sure it passes.
  • All of these should fail:
    • https://expired.badssl.com/
    • https://wrong.host.badssl.com/
    • https://untrusted-root.badssl.com/
    • https://revoked.badssl.com/
Friday, August 20, 2021
 
Blazemonger
answered 2 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :