Στα σενάρια PHP, είτε καλείτε την include()
, την require()
, την fopen()
, είτε τα παράγωγά τους, όπως η include_once
, η require_once
, ή ακόμα και η move_uploaded_file()
, συχνά συναντάτε ένα σφάλμα ή μια προειδοποίηση:
Failed to open stream : No such file or directory.
Ποια είναι μια καλή διαδικασία για να βρείτε γρήγορα τη βασική αιτία του προβλήματος;
Υπάρχουν πολλοί λόγοι για τους οποίους μπορεί να εμφανιστεί αυτό το σφάλμα και επομένως ένας καλός κατάλογος ελέγχου για το τι πρέπει να ελέγξετε πρώτα βοηθάει σημαντικά. Ας θεωρήσουμε ότι αντιμετωπίζουμε το πρόβλημα της ακόλουθης γραμμής:
require "/path/to/file"
<br>,
<br>,
require*
ή το include*
σε δική του μεταβλητή, κάντε echo, αντιγράψτε το και δοκιμάστε να το προσπελάσετε από ένα τερματικό:
$path = "/path/to/file",
echo "Path : $path",
require "$path",
Στη συνέχεια, σε ένα τερματικό:
cat <file path pasted>,
<br>,/users/tony/htdocs
.require __DIR__ . "/relative/path/from/current/file"
. Η μαγική σταθερά __DIR__
επιστρέφει τον κατάλογο του τρέχοντος αρχείου.SITE_ROOT
:config.php
config.php
, γράψτε
define('SITE_ROOT', DIR);config.php
και στη συνέχεια χρησιμοποιήστε τη σταθερά SITE_ROOT
όπου θέλετε :
require_once DIR."/../config.php",
...
require_once SITE_ROOT."/other/file.php",
Αυτές οι 2 πρακτικές κάνουν επίσης την εφαρμογή σας πιο φορητή επειδή δεν βασίζεται σε ρυθμίσεις ini όπως η διαδρομή include.
<br>,Ένας άλλος τρόπος για να συμπεριλάβετε αρχεία, ούτε σχετικά ούτε καθαρά απόλυτα, είναι να βασίζεστε στο include path. Αυτό συμβαίνει συχνά για βιβλιοθήκες ή πλαίσια όπως το πλαίσιο Zend. Μια τέτοια συμπερίληψη θα μοιάζει ως εξής :
include "Zend/Mail/Protocol/Imap.php"
Σε αυτή την περίπτωση, θα πρέπει να βεβαιωθείτε ότι ο φάκελος όπου βρίσκεται το "Zend", είναι μέρος της διαδρομής include. Μπορείτε να ελέγξετε τη διαδρομή include με την εντολή :
echo get_include_path();
Μπορείτε να προσθέσετε έναν φάκελο σε αυτό με :
set_include_path(get_include_path().":"."/path/to/new/folder");
<br>,
Μπορεί όλα μαζί, ο χρήστης που εκτελεί τη διεργασία του διακομιστή (Apache ή PHP) απλά να μην έχει δικαιώματα ανάγνωσης ή εγγραφής σε αυτό το αρχείο. Για να ελέγξετε κάτω από ποιον χρήστη εκτελείται ο διακομιστής μπορείτε να χρησιμοποιήσετε το posix_getpwuid :
$user = posix_getpwuid(posix_geteuid());
var_dump($user);
Για να μάθετε τα δικαιώματα στο αρχείο, πληκτρολογήστε την ακόλουθη εντολή στο τερματικό:
ls -l <path/to/file>
και κοιτάξτε συμβολική σημειογραφία δικαιωμάτων <br>,
Αν κανένα από τα παραπάνω δεν λειτούργησε, τότε το πρόβλημα είναι πιθανότατα ότι κάποιες ρυθμίσεις της PHP απαγορεύουν την πρόσβαση σε αυτό το αρχείο. Τρεις ρυθμίσεις θα μπορούσαν να είναι σχετικές :
phpinfo()
είτε χρησιμοποιώντας την ini_get("open_basedir")
ini_get("allow_url_include")
και να οριστεί με ini_set("allow_url_include", "1")
<br>,Αν κανένα από τα παραπάνω δεν επέτρεψε τη διάγνωση του προβλήματος, εδώ είναι μερικές ειδικές περιπτώσεις που θα μπορούσαν να συμβούν : <br>,
Μπορεί να συμβεί να συμπεριλάβετε μια βιβλιοθήκη, για παράδειγμα το πλαίσιο Zend, χρησιμοποιώντας μια σχετική ή απόλυτη διαδρομή. Για παράδειγμα :
require "/usr/share/php/libzend-framework-php/Zend/Mail/Protocol/Imap.php"
Αλλά και τότε εξακολουθεί να εμφανίζεται το ίδιο είδος σφάλματος. Αυτό θα μπορούσε να συμβεί επειδή το αρχείο που έχετε (επιτυχώς) συμπεριλάβει, έχει το ίδιο μια δήλωση include για ένα άλλο αρχείο, και αυτή η δεύτερη δήλωση include υποθέτει ότι έχετε προσθέσει τη διαδρομή αυτής της βιβλιοθήκης στη διαδρομή include. Για παράδειγμα, το αρχείο του πλαισίου Zend που αναφέρθηκε προηγουμένως θα μπορούσε να έχει την ακόλουθη include :
include "Zend/Mail/Protocol/Exception.php"
το οποίο δεν είναι ούτε συμπερίληψη με σχετική διαδρομή, ούτε με απόλυτη διαδρομή. Υποθέτει ότι ο κατάλογος Zend framework έχει προστεθεί στη διαδρομή include. Σε μια τέτοια περίπτωση, η μόνη πρακτική λύση είναι να προσθέσετε τον κατάλογο στη διαδρομή include. <br>,
Εάν εκτελείτε Linux με ενισχυμένη ασφάλεια, τότε μπορεί να είναι η αιτία του προβλήματος, αρνούμενη την πρόσβαση στο αρχείο από τον διακομιστή.
Για να ελέγξετε αν το SELinux είναι ενεργοποιημένο στο σύστημά σας, εκτελέστε την εντολή sestatus
σε ένα τερματικό. Εάν η εντολή δεν υπάρχει, τότε το SELinux δεν είναι ενεργοποιημένο στο σύστημά σας. Αν υπάρχει, τότε θα πρέπει να σας πει αν έχει επιβληθεί ή όχι.
Για να ελέγξετε αν η πολιτική SELinux είναι η αιτία του προβλήματος, μπορείτε να δοκιμάσετε να την απενεργοποιήσετε προσωρινά. Ωστόσο, να είστε ΠΡΟΣΕΧΤΙΚΟΙ, καθώς αυτό θα απενεργοποιήσει εντελώς την προστασία. Μην το κάνετε αυτό στο διακομιστή παραγωγής σας.
setenforce 0
Εάν δεν έχετε πλέον το πρόβλημα με την απενεργοποίηση του SELinux, τότε αυτή είναι η βασική αιτία. Για να το λύσετε, θα πρέπει να ρυθμίσετε το SELinux ανάλογα. Οι ακόλουθοι τύποι πλαισίου θα είναι απαραίτητοι :
httpd_sys_content_t
για τα αρχεία που θέλετε να μπορεί να διαβάσει ο διακομιστής σαςhttpd_sys_rw_content_t
για αρχεία στα οποία θέλετε να έχετε πρόσβαση ανάγνωσης και εγγραφήςhttpd_log_t
για τα αρχεία καταγραφήςhttpd_cache_t
για τον κατάλογο cache
Για παράδειγμα, για να εκχωρήσετε τον τύπο περιβάλλοντος httpd_sys_content_t
στον ριζικό κατάλογο του ιστοτόπου σας, εκτελέστε :semanage fcontext -a -t httpd_sys_content_t "/path/to/root(/.*)?"
restorecon -Rv /path/to/root
Εάν το αρχείο σας βρίσκεται σε έναν οικείο κατάλογο, θα πρέπει επίσης να ενεργοποιήσετε το boolean httpd_enable_homedirs
:
setsebool -P httpd_enable_homedirs 1
Σε κάθε περίπτωση, μπορεί να υπάρχουν διάφοροι λόγοι για τους οποίους το SELinux θα μπορούσε να αρνηθεί την πρόσβαση σε ένα αρχείο, ανάλογα με τις πολιτικές σας. Επομένως, θα πρέπει να το ερευνήσετε αυτό. Εδώ υπάρχει ένα σεμινάριο ειδικά για τη διαμόρφωση του SELinux για έναν διακομιστή ιστού. <br>,
Αν χρησιμοποιείτε Symfony και αντιμετωπίζετε αυτό το σφάλμα κατά το ανέβασμα σε έναν διακομιστή, τότε μπορεί να είναι ότι η cache της εφαρμογής δεν έχει μηδενιστεί, είτε επειδή έχει ανέβει η app/cache
, είτε επειδή η cache δεν έχει καθαριστεί.
Μπορείτε να το ελέγξετε και να το διορθώσετε εκτελώντας την ακόλουθη εντολή στην κονσόλα:
cache:clear
<br>,
Προφανώς, αυτό το σφάλμα μπορεί να συμβεί και κατά την κλήση της μεθόδου zip->close()
όταν κάποια αρχεία μέσα στο zip έχουν χαρακτήρες μη ASCII στο όνομα αρχείου τους, όπως "é".
Μια πιθανή λύση είναι να τυλίξετε το όνομα του αρχείου στην utf8_decode()
πριν από τη δημιουργία του αρχείου-στόχου.
Πιστίες στον Fran Cano για τον εντοπισμό και την πρόταση λύσης αυτού του προβλήματος
Για να προσθέσω στην (πραγματικά καλή) υπάρχουσα απάντηση
Το open_basedir
είναι αυτό που μπορεί να σας βάλει σε αμηχανία, επειδή μπορεί να καθοριστεί σε μια διαμόρφωση του διακομιστή ιστού. Ενώ αυτό διορθώνεται εύκολα αν τρέχετε τον δικό σας dedicated server, υπάρχουν κάποια πακέτα λογισμικού shared hosting εκεί έξω (όπως το Plesk, το cPanel κ.λπ.) που θα ρυθμίσουν μια οδηγία διαμόρφωσης σε βάση ανά domain. Επειδή το λογισμικό δημιουργεί το αρχείο διαμόρφωσης (π.χ. httpd.conf
) δεν μπορείτε να αλλάξετε αυτό το αρχείο άμεσα, επειδή το λογισμικό φιλοξενίας θα το αντικαταστήσει όταν επανεκκινηθεί.
Με το Plesk, παρέχουν ένα μέρος για να παρακάμψετε το παρεχόμενο httpd.conf
που ονομάζεται vhost.conf
. Μόνο ο διαχειριστής του διακομιστή μπορεί να γράψει αυτό το αρχείο. Η διαμόρφωση για τον Apache μοιάζει κάπως έτσι
<Directory /var/www/vhosts/domain.com>
<IfModule mod_php5.c>
php_admin_flag engine on
php_admin_flag safe_mode off
php_admin_value open_basedir "/var/www/vhosts/domain.com:/tmp:/usr/share/pear:/local/PEAR"
</IfModule>
</Directory>
Ζητήστε από τον διαχειριστή του διακομιστή σας να συμβουλευτεί το εγχειρίδιο για το λογισμικό φιλοξενίας και το λογισμικό διακομιστή ιστού που χρησιμοποιεί.
Είναι σημαντικό να σημειωθεί ότι η εκτέλεση ενός αρχείου μέσω του διακομιστή ιστού σας είναι πολύ διαφορετική από την εκτέλεση μιας γραμμής εντολών ή μιας εργασίας cron. Η μεγάλη διαφορά είναι ότι ο διακομιστής ιστού σας έχει το δικό του χρήστη και τα δικά του δικαιώματα. Για λόγους ασφαλείας αυτός ο χρήστης είναι αρκετά περιορισμένος. Ο Apache, για παράδειγμα, είναι συχνά apache
, www-data
ή httpd
(ανάλογα με το διακομιστή σας). Μια εργασία cron ή μια εκτέλεση CLI έχει τα δικαιώματα που έχει ο χρήστης που την εκτελεί (π.χ. η εκτέλεση ενός PHP script ως root θα εκτελεστεί με δικαιώματα root).
Πολλές φορές οι άνθρωποι λύνουν ένα πρόβλημα δικαιωμάτων κάνοντας τα εξής (παράδειγμα Linux)
chmod 777 /path/to/file
Αυτό δεν είναι μια έξυπνη ιδέα, επειδή το αρχείο ή ο κατάλογος είναι πλέον εγγράψιμος σε όλο τον κόσμο. Αν είστε ο ιδιοκτήτης του διακομιστή και ο μοναδικός χρήστης, τότε αυτό δεν είναι τόσο σημαντικό, αλλά αν είστε σε ένα κοινόχρηστο περιβάλλον φιλοξενίας, μόλις δώσατε πρόσβαση σε όλους τους χρήστες του διακομιστή σας.
Αυτό που πρέπει να κάνετε είναι να καθορίσετε τους χρήστες που χρειάζονται πρόσβαση και να δώσετε πρόσβαση μόνο σε αυτούς. Μόλις μάθετε ποιοι χρήστες χρειάζονται πρόσβαση, θα θέλετε να βεβαιωθείτε ότι
Αυτός ο χρήστης κατέχει το αρχείο και ενδεχομένως τον γονικό κατάλογο (ειδικά τον γονικό κατάλογο αν θέλετε να γράψετε αρχεία). Στα περισσότερα περιβάλλοντα shared hosting αυτό δεν θα'ειναι πρόβλημα, επειδή ο χρήστης σας θα πρέπει να κατέχει όλα τα αρχεία κάτω από το root σας. Ένα παράδειγμα για το Linux παρουσιάζεται παρακάτω
chown apache:apache /path/to/file
Ο χρήστης, και μόνο αυτός ο χρήστης, έχει πρόσβαση. Στο Linux, μια καλή πρακτική θα ήταν chmod 600
(μόνο ο ιδιοκτήτης μπορεί να διαβάσει και να γράψει) ή chmod 644
(ο ιδιοκτήτης μπορεί να γράψει αλλά όλοι μπορούν να διαβάσουν)
Μπορείτε να διαβάσετε μια πιο εκτεταμένη συζήτηση για τα δικαιώματα και τους χρήστες του Linux/Unix εδώ
Μια άλλη πιθανή αιτία: Μετονομασία ή/και μετακίνηση αρχείων ενώ βρίσκεστε σε επεξεργαστή κειμένου. Έκανα όλα τα παραπάνω βήματα χωρίς επιτυχία μέχρι που διέγραψα το αρχείο που συνέχισε να προκαλεί αυτό το σφάλμα και δημιούργησα ένα νέο, το οποίο διόρθωσε το πρόβλημα.